conjur-asset-ui-beta 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.git-hooks/pre_commit/trailing_whitespace.rb +26 -0
- data/.gitignore +23 -0
- data/.project +18 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +76 -0
- data/TODO.md +31 -0
- data/app/.csscomb.json +304 -0
- data/app/.jshintrc +46 -0
- data/app/build/css/bootstrap.css +6906 -0
- data/app/build/fonts/glyphicons-halflings-regular.eot +0 -0
- data/app/build/fonts/glyphicons-halflings-regular.svg +288 -0
- data/app/build/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/app/build/fonts/glyphicons-halflings-regular.woff +0 -0
- data/app/build/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/app/build/images/conjur-logo.svg +26 -0
- data/app/build/images/icon-client-pc.svg +12 -0
- data/app/build/images/icon-environment.png +0 -0
- data/app/build/images/icon-person.svg +12 -0
- data/app/build/images/icon-policy.png +0 -0
- data/app/build/images/icon-resource.png +0 -0
- data/app/build/images/icon-service-dots.svg +13 -0
- data/app/build/images/icon-variable.png +0 -0
- data/app/build/index.html +26 -0
- data/app/build/js/app.js +78070 -0
- data/app/build/js/pace.js +2 -0
- data/app/config/preprocessor.js +9 -0
- data/app/config/webpack.js +84 -0
- data/app/gulpfile.js +144 -0
- data/app/package.json +83 -0
- data/app/src/actions.js +493 -0
- data/app/src/app.js +76 -0
- data/app/src/clients/audit.js +54 -0
- data/app/src/clients/generic.js +87 -0
- data/app/src/clients/layer_members.js +36 -0
- data/app/src/clients/list.js +82 -0
- data/app/src/clients/members.js +37 -0
- data/app/src/clients/search.js +19 -0
- data/app/src/components/app/__tests__/app-test.js +22 -0
- data/app/src/components/app/app.js +66 -0
- data/app/src/components/audit/__tests__/table_header-test.js +40 -0
- data/app/src/components/audit/box.js +11 -0
- data/app/src/components/audit/constants.js +7 -0
- data/app/src/components/audit/entry.js +107 -0
- data/app/src/components/audit/fields_mixin.js +13 -0
- data/app/src/components/audit/humanize_event.js +216 -0
- data/app/src/components/audit/table.js +100 -0
- data/app/src/components/audit/table_header.js +38 -0
- data/app/src/components/audit/timestamp.js +30 -0
- data/app/src/components/chart/chart.js +539 -0
- data/app/src/components/chart/chart_helper_mixin.js +79 -0
- data/app/src/components/custom/list.js +5 -0
- data/app/src/components/custom/view.js +71 -0
- data/app/src/components/dashboard/activity.js +113 -0
- data/app/src/components/dashboard/dashboard.js +47 -0
- data/app/src/components/flash/flash.js +17 -0
- data/app/src/components/generic/__tests__/time-test.js +43 -0
- data/app/src/components/generic/annotations.js +41 -0
- data/app/src/components/generic/breadcrumbs.js +59 -0
- data/app/src/components/generic/foldable_audit_section.js +252 -0
- data/app/src/components/generic/list.js +144 -0
- data/app/src/components/generic/list_factory.js +42 -0
- data/app/src/components/generic/resource_link.js +65 -0
- data/app/src/components/generic/role_link.js +65 -0
- data/app/src/components/generic/tab_mixin.js +148 -0
- data/app/src/components/generic/time.js +34 -0
- data/app/src/components/group/list.js +5 -0
- data/app/src/components/group/view.js +137 -0
- data/app/src/components/host/activity.js +93 -0
- data/app/src/components/host/details.js +30 -0
- data/app/src/components/host/host_link.js +20 -0
- data/app/src/components/host/list.js +5 -0
- data/app/src/components/host/view.js +113 -0
- data/app/src/components/layer/list.js +5 -0
- data/app/src/components/layer/view.js +180 -0
- data/app/src/components/navbar/__tests__/navbar-test.js +21 -0
- data/app/src/components/navbar/nav_search_form.js +41 -0
- data/app/src/components/navbar/navbar.js +71 -0
- data/app/src/components/owned_resources/owned_resources.js +86 -0
- data/app/src/components/owned_resources/owned_resources_box.js +106 -0
- data/app/src/components/permissions/permissions.js +143 -0
- data/app/src/components/permissions/permissions_table.js +104 -0
- data/app/src/components/policy/list.js +5 -0
- data/app/src/components/policy/view.js +98 -0
- data/app/src/components/refresh/refresh.js +30 -0
- data/app/src/components/refresh/refresh.less +15 -0
- data/app/src/components/search/group.js +45 -0
- data/app/src/components/search/group_heading.js +50 -0
- data/app/src/components/search/group_title.js +38 -0
- data/app/src/components/search/result_item.js +57 -0
- data/app/src/components/search/search.js +103 -0
- data/app/src/components/user/activity.js +92 -0
- data/app/src/components/user/details.js +30 -0
- data/app/src/components/user/list.js +5 -0
- data/app/src/components/user/pubkeys.js +116 -0
- data/app/src/components/user/pubkeys.less +56 -0
- data/app/src/components/user/view.js +123 -0
- data/app/src/components/variable/activity.js +83 -0
- data/app/src/components/variable/details.js +48 -0
- data/app/src/components/variable/fetchers.js +83 -0
- data/app/src/components/variable/list.js +5 -0
- data/app/src/components/variable/updaters.js +83 -0
- data/app/src/components/variable/view.js +105 -0
- data/app/src/constants.js +35 -0
- data/app/src/images/conjur-logo.svg +26 -0
- data/app/src/images/icon-client-pc.svg +12 -0
- data/app/src/images/icon-environment.png +0 -0
- data/app/src/images/icon-person.svg +12 -0
- data/app/src/images/icon-policy.png +0 -0
- data/app/src/images/icon-resource.png +0 -0
- data/app/src/images/icon-service-dots.svg +13 -0
- data/app/src/images/icon-variable.png +0 -0
- data/app/src/pages/index.html +26 -0
- data/app/src/routes.js +57 -0
- data/app/src/stores/app_store.js +29 -0
- data/app/src/stores/audit_store.js +77 -0
- data/app/src/stores/group_store.js +105 -0
- data/app/src/stores/host_store.js +98 -0
- data/app/src/stores/layer_store.js +115 -0
- data/app/src/stores/policy_store.js +89 -0
- data/app/src/stores/resources_store.js +118 -0
- data/app/src/stores/route_store.js +24 -0
- data/app/src/stores/search_store.js +73 -0
- data/app/src/stores/user_store.js +111 -0
- data/app/src/stores/variable_store.js +94 -0
- data/app/src/styles/bootstrap.less +56 -0
- data/app/src/styles/styles.less +634 -0
- data/app/src/utils.js +43 -0
- data/app/src/vendor/pace.js +2 -0
- data/conjur-asset-ui.gemspec +36 -0
- data/features/navigation_bar.feature +31 -0
- data/features/step_definitions/custom_step.rb +32 -0
- data/features/support/env.rb +38 -0
- data/features/support/hooks.rb +30 -0
- data/features/support/world.rb +17 -0
- data/lib/conjur-asset-ui-version.rb +7 -0
- data/lib/conjur-asset-ui.rb +7 -0
- data/lib/conjur/command/ui.rb +54 -0
- data/lib/conjur/webserver/api_proxy.rb +94 -0
- data/lib/conjur/webserver/authorize.rb +28 -0
- data/lib/conjur/webserver/conjur_info.rb +33 -0
- data/lib/conjur/webserver/home.rb +42 -0
- data/lib/conjur/webserver/login.rb +57 -0
- data/lib/conjur/webserver/renderer.rb +34 -0
- data/lib/conjur/webserver/server.rb +130 -0
- data/public/js/views/roleGraph.js +91 -0
- metadata +373 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var d3 = require('d3'),
|
4
|
+
forEach = require('lodash/collection/forEach');
|
5
|
+
|
6
|
+
var oneDay = 86400000;
|
7
|
+
|
8
|
+
module.exports = {
|
9
|
+
getData(audit) {
|
10
|
+
var values = {},
|
11
|
+
min,
|
12
|
+
max,
|
13
|
+
self = this;
|
14
|
+
|
15
|
+
forEach(audit, function(e) {
|
16
|
+
var date,
|
17
|
+
type,
|
18
|
+
value;
|
19
|
+
|
20
|
+
if (typeof e.timestamp === 'string') {
|
21
|
+
date = d3.time.hour(new Date(e.timestamp));
|
22
|
+
}
|
23
|
+
|
24
|
+
type = self.getEventType(e);
|
25
|
+
|
26
|
+
if (date instanceof Date && typeof type === 'string') {
|
27
|
+
value = values[date.toString()];
|
28
|
+
|
29
|
+
if (typeof value === 'undefined') {
|
30
|
+
value = self.getDefaultItem();
|
31
|
+
value.date = date;
|
32
|
+
}
|
33
|
+
|
34
|
+
value[type] = value[type] + 1;
|
35
|
+
|
36
|
+
values[date.toString()] = value;
|
37
|
+
|
38
|
+
if (typeof max === 'undefined') {
|
39
|
+
max = date;
|
40
|
+
}
|
41
|
+
|
42
|
+
if (typeof min === 'undefined') {
|
43
|
+
min = date;
|
44
|
+
}
|
45
|
+
|
46
|
+
if (date > max) {
|
47
|
+
max = date;
|
48
|
+
}
|
49
|
+
|
50
|
+
if (date < min) {
|
51
|
+
min = date;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
});
|
55
|
+
|
56
|
+
var x = max - min;
|
57
|
+
|
58
|
+
if (x < oneDay) {
|
59
|
+
var delta = Math.round((86400000 - x) / 2);
|
60
|
+
|
61
|
+
min = new Date(min.getTime() - delta);
|
62
|
+
max = new Date(max.getTime() + delta);
|
63
|
+
}
|
64
|
+
|
65
|
+
return d3.time.scale()
|
66
|
+
.domain([min, max])
|
67
|
+
.ticks(d3.time.hours, 1)
|
68
|
+
.map(function(date) {
|
69
|
+
var d = values[date.toString()] || self.getDefaultItem();
|
70
|
+
|
71
|
+
d.date = date;
|
72
|
+
|
73
|
+
return d;
|
74
|
+
})
|
75
|
+
.sort(function(a, b) {
|
76
|
+
return a.date - b.date;
|
77
|
+
});
|
78
|
+
}
|
79
|
+
};
|
@@ -0,0 +1,71 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react'),
|
4
|
+
Fluxxor = require('fluxxor'),
|
5
|
+
FluxMixin = Fluxxor.FluxMixin(React),
|
6
|
+
StoreWatchMixin = Fluxxor.StoreWatchMixin,
|
7
|
+
Router = require('react-router');
|
8
|
+
|
9
|
+
var AuditTable = require('../audit/table'),
|
10
|
+
Annotations = require('../generic/annotations'),
|
11
|
+
Breadcrumbs = require('../generic/breadcrumbs'),
|
12
|
+
FoldableAuditSection = require('../generic/foldable_audit_section'),
|
13
|
+
Refresh = require('../refresh/refresh');
|
14
|
+
|
15
|
+
module.exports = React.createClass({
|
16
|
+
displayName: 'CustomView',
|
17
|
+
|
18
|
+
mixins: [FluxMixin, StoreWatchMixin('audit', 'resources'), Router.State],
|
19
|
+
|
20
|
+
getStateFromFlux() {
|
21
|
+
var flux = this.getFlux(),
|
22
|
+
kind = unescape(this.getParams().kind),
|
23
|
+
id = unescape(this.getParams().id),
|
24
|
+
audit = flux.store('audit').getEventFor(kind, id),
|
25
|
+
resource = flux.store('resources').getResource(kind, id);
|
26
|
+
|
27
|
+
return {
|
28
|
+
loading: {
|
29
|
+
audit: audit.loading,
|
30
|
+
resource: resource.loading
|
31
|
+
},
|
32
|
+
error: {
|
33
|
+
resource: resource.error
|
34
|
+
},
|
35
|
+
audit: audit.data,
|
36
|
+
resource: resource.data
|
37
|
+
};
|
38
|
+
},
|
39
|
+
|
40
|
+
render() {
|
41
|
+
var elems = [
|
42
|
+
{url: '/ui/custom-types', text: 'Custom Types'},
|
43
|
+
{url: '', text: this.state.resource.id},
|
44
|
+
{url: '', text: '(owned by ' + this.state.resource.owner + ')'}
|
45
|
+
];
|
46
|
+
|
47
|
+
return (
|
48
|
+
<div className="b-variable">
|
49
|
+
<Breadcrumbs elems={elems} />
|
50
|
+
<hr />
|
51
|
+
<h2>Custom Type {this.state.resource.kind}:{this.state.resource.identifier}</h2>
|
52
|
+
<hr/>
|
53
|
+
<h2>Annotations<Refresh show={this.state.loading.resource} /></h2>
|
54
|
+
<Annotations annotations={this.state.resource.annotations} />
|
55
|
+
<hr />
|
56
|
+
<AuditTable events={this.state.audit}
|
57
|
+
caption="Recent Activity"
|
58
|
+
isLoading={this.state.loading.audit} />
|
59
|
+
</div>
|
60
|
+
);
|
61
|
+
},
|
62
|
+
|
63
|
+
componentDidMount() {
|
64
|
+
var actions = this.getFlux().actions,
|
65
|
+
kind = unescape(this.getParams().kind),
|
66
|
+
id = unescape(this.getParams().id);
|
67
|
+
|
68
|
+
actions.audit.loadForResource(kind, id);
|
69
|
+
actions.resources.loadOne(kind, id);
|
70
|
+
}
|
71
|
+
});
|
@@ -0,0 +1,113 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react');
|
4
|
+
|
5
|
+
var Chart = require('../chart/chart'),
|
6
|
+
ChartHelperMixin = require('../chart/chart_helper_mixin'),
|
7
|
+
Refresh = require('../refresh/refresh');
|
8
|
+
|
9
|
+
module.exports = React.createClass({
|
10
|
+
displayName: 'DashboardActivity',
|
11
|
+
|
12
|
+
mixins: [ChartHelperMixin],
|
13
|
+
|
14
|
+
getDefaultProps() {
|
15
|
+
return {
|
16
|
+
options: {
|
17
|
+
legend: {
|
18
|
+
slogins: 'Logins',
|
19
|
+
ssudo: 'Sudo calls',
|
20
|
+
sreads: 'Secret reads',
|
21
|
+
supdates: 'Secret updates',
|
22
|
+
fsudo: 'Sudo failures',
|
23
|
+
freads: 'Secret read failures',
|
24
|
+
fupdates: 'Secret update failures',
|
25
|
+
fother: 'Other failures'
|
26
|
+
},
|
27
|
+
axis: {
|
28
|
+
y: {
|
29
|
+
label: 'Value'
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
};
|
34
|
+
},
|
35
|
+
|
36
|
+
getDefaultItem() {
|
37
|
+
return {
|
38
|
+
slogins: 0,
|
39
|
+
ssudo: 0,
|
40
|
+
fsudo: 0,
|
41
|
+
sreads: 0,
|
42
|
+
freads: 0,
|
43
|
+
supdates: 0,
|
44
|
+
fupdates: 0,
|
45
|
+
fother: 0
|
46
|
+
};
|
47
|
+
},
|
48
|
+
|
49
|
+
getEventType(e) {
|
50
|
+
if (e.hasOwnProperty('facility') && e.facility === 'ssh') {
|
51
|
+
if (e.allowed === true) {
|
52
|
+
if (e.action === 'sudo') {
|
53
|
+
return 'ssudo';
|
54
|
+
}
|
55
|
+
|
56
|
+
if (e.action === 'login') {
|
57
|
+
return 'slogins';
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
if (e.allowed === false) {
|
62
|
+
if (e.action === 'sudo') {
|
63
|
+
return 'fsudo';
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
if ((e.action === 'check') &&
|
69
|
+
e.hasOwnProperty('resource') &&
|
70
|
+
e.resource.split(':')[1] === 'variable') {
|
71
|
+
|
72
|
+
if (e.hasOwnProperty('error') || e.allowed === false) {
|
73
|
+
if (e.privilege === 'execute') {
|
74
|
+
return 'freads';
|
75
|
+
}
|
76
|
+
|
77
|
+
if (e.privilege === 'update') {
|
78
|
+
return 'fupdates';
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
if (!e.hasOwnProperty('error') && e.allowed !== false) {
|
83
|
+
if (e.privilege === 'execute') {
|
84
|
+
return 'sreads';
|
85
|
+
}
|
86
|
+
|
87
|
+
if (e.privilege === 'update') {
|
88
|
+
return 'supdates';
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
if (e.hasOwnProperty('error') || e.allowed === false) {
|
94
|
+
return 'fother';
|
95
|
+
}
|
96
|
+
|
97
|
+
return null;
|
98
|
+
},
|
99
|
+
|
100
|
+
render() {
|
101
|
+
var data = this.getData(this.props.audit);
|
102
|
+
|
103
|
+
return (
|
104
|
+
<div className="b-dashboard-activity">
|
105
|
+
<h2>Activity<Refresh show={this.props.isLoading} /></h2>
|
106
|
+
<div className="b-dashboard-activity__graph">
|
107
|
+
<Chart options={this.props.options}
|
108
|
+
data={data} />
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
);
|
112
|
+
}
|
113
|
+
});
|
@@ -0,0 +1,47 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react'),
|
4
|
+
Fluxxor = require('fluxxor'),
|
5
|
+
FluxMixin = Fluxxor.FluxMixin(React),
|
6
|
+
StoreWatchMixin = Fluxxor.StoreWatchMixin;
|
7
|
+
|
8
|
+
var AuditTable = require('../audit/table'),
|
9
|
+
FoldableAuditSection = require('../generic/foldable_audit_section');
|
10
|
+
|
11
|
+
var Activity = require('./activity');
|
12
|
+
|
13
|
+
module.exports = React.createClass({
|
14
|
+
displayName: 'Dashboard',
|
15
|
+
|
16
|
+
mixins: [FluxMixin, StoreWatchMixin('audit')],
|
17
|
+
|
18
|
+
getStateFromFlux() {
|
19
|
+
var audit = this.getFlux().store('audit').getAllEvents();
|
20
|
+
|
21
|
+
return {
|
22
|
+
audit: audit.data,
|
23
|
+
loading: audit.loading
|
24
|
+
};
|
25
|
+
},
|
26
|
+
|
27
|
+
render() {
|
28
|
+
return (
|
29
|
+
<div className="b-dashboard">
|
30
|
+
<Activity audit={this.state.audit}
|
31
|
+
isLoading={this.state.loading} />
|
32
|
+
<hr />
|
33
|
+
{FoldableAuditSection.warnings(this.state.audit, this.state.loading)}
|
34
|
+
<hr />
|
35
|
+
{FoldableAuditSection.changes(this.state.audit, this.state.loading)}
|
36
|
+
<hr />
|
37
|
+
<AuditTable events={this.state.audit}
|
38
|
+
caption="Recent activity"
|
39
|
+
isLoading={this.state.loading} />
|
40
|
+
</div>
|
41
|
+
);
|
42
|
+
},
|
43
|
+
|
44
|
+
componentDidMount() {
|
45
|
+
this.getFlux().actions.audit.loadAll();
|
46
|
+
}
|
47
|
+
});
|
@@ -0,0 +1,17 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react');
|
4
|
+
|
5
|
+
module.exports = React.createClass({
|
6
|
+
displayName: 'Flash',
|
7
|
+
|
8
|
+
render() {
|
9
|
+
return (
|
10
|
+
<div className="alert alert-danger alert-dismissable">
|
11
|
+
<button type="button" className="close" aria-hidden="true"
|
12
|
+
onClick={this.props.handleClose}>×</button>
|
13
|
+
<span className="text">{this.props.msg}</span>
|
14
|
+
</div>
|
15
|
+
);
|
16
|
+
}
|
17
|
+
});
|
@@ -0,0 +1,43 @@
|
|
1
|
+
/* global jest, describe, it, expect */
|
2
|
+
|
3
|
+
'use strict';
|
4
|
+
|
5
|
+
jest.dontMock('../time');
|
6
|
+
jest.dontMock('moment');
|
7
|
+
|
8
|
+
var React = require('react/addons'),
|
9
|
+
TestUtils = React.addons.TestUtils,
|
10
|
+
Time = require('../time').Time;
|
11
|
+
|
12
|
+
describe('time', function() {
|
13
|
+
it('default render', function() {
|
14
|
+
var component = TestUtils.renderIntoDocument(
|
15
|
+
<Time timestamp="1" />
|
16
|
+
);
|
17
|
+
|
18
|
+
var element = TestUtils.findRenderedDOMComponentWithTag(component, 'time');
|
19
|
+
|
20
|
+
expect(element.getDOMNode().textContent).toEqual('Jan 1, 1970 3:00 AM');
|
21
|
+
});
|
22
|
+
|
23
|
+
|
24
|
+
it('time from now', function() {
|
25
|
+
var component = TestUtils.renderIntoDocument(
|
26
|
+
<Time timestamp="1" relative="time from now" />
|
27
|
+
);
|
28
|
+
|
29
|
+
var element = TestUtils.findRenderedDOMComponentWithTag(component, 'time');
|
30
|
+
|
31
|
+
expect(element.getDOMNode().textContent).toMatch(/years ago/);
|
32
|
+
});
|
33
|
+
|
34
|
+
it('calendar time', function() {
|
35
|
+
var component = TestUtils.renderIntoDocument(
|
36
|
+
<Time timestamp="1" relative="calendar time" />
|
37
|
+
);
|
38
|
+
|
39
|
+
var element = TestUtils.findRenderedDOMComponentWithTag(component, 'time');
|
40
|
+
|
41
|
+
expect(element.getDOMNode().textContent).toEqual('01/01/1970');
|
42
|
+
});
|
43
|
+
});
|
@@ -0,0 +1,41 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react');
|
4
|
+
|
5
|
+
module.exports = React.createClass({
|
6
|
+
displayName: 'GenericAnnotations',
|
7
|
+
|
8
|
+
render() {
|
9
|
+
if (!this.props.annotations || this.props.annotations.length === 0) {
|
10
|
+
return (
|
11
|
+
<div>
|
12
|
+
<span>None</span>
|
13
|
+
</div>
|
14
|
+
);
|
15
|
+
}
|
16
|
+
|
17
|
+
// TODO: sort by date (optionally)
|
18
|
+
// TODO: table view
|
19
|
+
var annotationsList = [];
|
20
|
+
|
21
|
+
this.props.annotations.sort(function(a, b) {
|
22
|
+
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
23
|
+
}).forEach(function(item) {
|
24
|
+
var itemName = item.name,
|
25
|
+
itemValue = item.value,
|
26
|
+
term = 'annotation_' + itemName + '_key',
|
27
|
+
description = 'annotation_' + itemName + '_value';
|
28
|
+
|
29
|
+
annotationsList.push(<dt key={term}>{itemName}</dt>);
|
30
|
+
annotationsList.push(<dd key={description}>{itemValue}</dd>);
|
31
|
+
});
|
32
|
+
|
33
|
+
return (
|
34
|
+
<div>
|
35
|
+
<dl className="annotations">
|
36
|
+
{annotationsList}
|
37
|
+
</dl>
|
38
|
+
</div>
|
39
|
+
);
|
40
|
+
}
|
41
|
+
});
|
@@ -0,0 +1,59 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
var React = require('react');
|
4
|
+
|
5
|
+
var map = require('lodash/collection/map');
|
6
|
+
|
7
|
+
var Breadcrumb = React.createClass({
|
8
|
+
displayName: 'Breadcrumb',
|
9
|
+
|
10
|
+
render() {
|
11
|
+
var className = '',
|
12
|
+
text;
|
13
|
+
|
14
|
+
if (this.props.active === true) {
|
15
|
+
className = 'active';
|
16
|
+
text = this.props.text;
|
17
|
+
|
18
|
+
} else if (this.props.extra === true) {
|
19
|
+
className = 'b-breadcrumb__extra';
|
20
|
+
text = this.props.text;
|
21
|
+
} else {
|
22
|
+
text = (<a href={this.props.url}>{this.props.text}</a>);
|
23
|
+
}
|
24
|
+
|
25
|
+
return (
|
26
|
+
<li className={className}>
|
27
|
+
{text}
|
28
|
+
</li>
|
29
|
+
);
|
30
|
+
}
|
31
|
+
});
|
32
|
+
|
33
|
+
module.exports = React.createClass({
|
34
|
+
displayName: 'Breadcrumbs',
|
35
|
+
|
36
|
+
render() {
|
37
|
+
var items = map(this.props.elems || [], function(e, idx, coll) {
|
38
|
+
var length = coll.length - 1,
|
39
|
+
active = idx + 1 === length ? true : false,
|
40
|
+
extra = idx === length ? true : false;
|
41
|
+
|
42
|
+
return (
|
43
|
+
<Breadcrumb url={e.url}
|
44
|
+
text={e.text}
|
45
|
+
active={active}
|
46
|
+
extra={extra}
|
47
|
+
key={e.url + e.text} />
|
48
|
+
);
|
49
|
+
});
|
50
|
+
|
51
|
+
return (
|
52
|
+
<div className="b-breadcrumbs">
|
53
|
+
<ol className="breadcrumb">
|
54
|
+
{items}
|
55
|
+
</ol>
|
56
|
+
</div>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
});
|