conjur-asset-ui 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.git-hooks/pre_commit/ensure_livescript_compiled.rb +31 -0
  3. data/.git-hooks/pre_commit/trailing_whitespace.rb +26 -0
  4. data/.gitignore +20 -0
  5. data/.overcommit.yml +5 -0
  6. data/.project +18 -0
  7. data/CHANGELOG.md +3 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +41 -0
  11. data/Rakefile +3 -0
  12. data/compile_ls +6 -0
  13. data/conjur-asset-ui.gemspec +37 -0
  14. data/lib/conjur/command/ui.rb +46 -0
  15. data/lib/conjur/webserver/api_proxy.rb +94 -0
  16. data/lib/conjur/webserver/authorize.rb +28 -0
  17. data/lib/conjur/webserver/conjur_info.rb +33 -0
  18. data/lib/conjur/webserver/home.rb +42 -0
  19. data/lib/conjur/webserver/login.rb +57 -0
  20. data/lib/conjur/webserver/renderer.rb +34 -0
  21. data/lib/conjur/webserver/server.rb +113 -0
  22. data/lib/conjur-asset-ui-version.rb +7 -0
  23. data/lib/conjur-asset-ui.rb +7 -0
  24. data/livescript/views/audit.ls +136 -0
  25. data/public/_client_code.html +42 -0
  26. data/public/_client_libs.html +24 -0
  27. data/public/css/bootstrap.css +7 -0
  28. data/public/css/styles.less +461 -0
  29. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  30. data/public/fonts/glyphicons-halflings-regular.svg +229 -0
  31. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  32. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  33. data/public/images/conjur-logo.svg +26 -0
  34. data/public/images/icon-client-pc.svg +12 -0
  35. data/public/images/icon-environment.png +0 -0
  36. data/public/images/icon-person.svg +12 -0
  37. data/public/images/icon-policy.png +0 -0
  38. data/public/images/icon-resource.png +0 -0
  39. data/public/images/icon-service-dots.svg +13 -0
  40. data/public/images/icon-variable.png +0 -0
  41. data/public/index.html.erb +62 -0
  42. data/public/js/init.js +107 -0
  43. data/public/js/lib/JSXTransformer.js +10862 -0
  44. data/public/js/lib/async.js +958 -0
  45. data/public/js/lib/backbone.js +2 -0
  46. data/public/js/lib/bootstrap.js +6 -0
  47. data/public/js/lib/date.extensions.js +141 -0
  48. data/public/js/lib/less.js +16 -0
  49. data/public/js/lib/moment.js +7768 -0
  50. data/public/js/lib/pace.js +2 -0
  51. data/public/js/lib/prelude-browser-min.js +1 -0
  52. data/public/js/lib/react-bootstrap.js +5346 -0
  53. data/public/js/lib/react-bootstrap.min.js +4 -0
  54. data/public/js/lib/sorted-set.no-require.js +1170 -0
  55. data/public/js/lib/sorted-set.no-require.js.txt +6 -0
  56. data/public/js/lib/underscore-min.js +6 -0
  57. data/public/js/lib/underscore.string.min.js +1 -0
  58. data/public/js/main.js +57 -0
  59. data/public/js/models/groupRecord.js +70 -0
  60. data/public/js/models/hostRecord.js +55 -0
  61. data/public/js/models/layerRecord.js +77 -0
  62. data/public/js/models/namespace.js +10 -0
  63. data/public/js/models/policyList.js +14 -0
  64. data/public/js/models/policyRecord.js +54 -0
  65. data/public/js/models/record.js +85 -0
  66. data/public/js/models/resourceList.js +69 -0
  67. data/public/js/models/userList.js +20 -0
  68. data/public/js/models/userRecord.js +70 -0
  69. data/public/js/models/variableList.js +16 -0
  70. data/public/js/models/variableRecord.js +73 -0
  71. data/public/js/routers.js +205 -0
  72. data/public/js/views/annotations.js +36 -0
  73. data/public/js/views/audit.js +363 -0
  74. data/public/js/views/dashboard.js +67 -0
  75. data/public/js/views/generic.js +115 -0
  76. data/public/js/views/group.js +61 -0
  77. data/public/js/views/groups.js +30 -0
  78. data/public/js/views/host.js +64 -0
  79. data/public/js/views/hosts.js +30 -0
  80. data/public/js/views/layer.js +92 -0
  81. data/public/js/views/layers.js +30 -0
  82. data/public/js/views/mixins/search.js +15 -0
  83. data/public/js/views/mixins/tabs.js +114 -0
  84. data/public/js/views/namespaces.js +40 -0
  85. data/public/js/views/navSearch.js +25 -0
  86. data/public/js/views/owned.js +178 -0
  87. data/public/js/views/permissions.js +188 -0
  88. data/public/js/views/policies.js +28 -0
  89. data/public/js/views/policy.js +43 -0
  90. data/public/js/views/resource.js +39 -0
  91. data/public/js/views/role.js +41 -0
  92. data/public/js/views/searchResults.js +145 -0
  93. data/public/js/views/time.js +14 -0
  94. data/public/js/views/user.js +68 -0
  95. data/public/js/views/users.js +31 -0
  96. data/public/js/views/variable.js +70 -0
  97. data/public/js/views/variables.js +30 -0
  98. data/spec/javascripts/helpers/.gitkeep +0 -0
  99. data/spec/javascripts/support/jasmine.yml +112 -0
  100. data/spec/javascripts/support/jasmine_helper.rb +22 -0
  101. data/spec/javascripts/support/run.html.erb +23 -0
  102. data/spec/javascripts/views/AuditSpec.js +22 -0
  103. data/spec/javascripts/views/AuditSpec.ls +18 -0
  104. data/vendor/prelude-ls/.gitignore +2 -0
  105. data/vendor/prelude-ls/.travis.yml +3 -0
  106. data/vendor/prelude-ls/CHANGELOG.md +81 -0
  107. data/vendor/prelude-ls/LICENSE +22 -0
  108. data/vendor/prelude-ls/Makefile +50 -0
  109. data/vendor/prelude-ls/README.md +15 -0
  110. data/vendor/prelude-ls/browser/prelude-browser-min.js +1 -0
  111. data/vendor/prelude-ls/browser/prelude-browser.js +1172 -0
  112. data/vendor/prelude-ls/lib/Func.js +40 -0
  113. data/vendor/prelude-ls/lib/List.js +602 -0
  114. data/vendor/prelude-ls/lib/Num.js +129 -0
  115. data/vendor/prelude-ls/lib/Obj.js +153 -0
  116. data/vendor/prelude-ls/lib/Str.js +68 -0
  117. data/vendor/prelude-ls/lib/index.js +164 -0
  118. data/vendor/prelude-ls/package.json +50 -0
  119. data/vendor/prelude-ls/package.ls +46 -0
  120. data/vendor/prelude-ls/src/Func.ls +17 -0
  121. data/vendor/prelude-ls/src/List.ls +299 -0
  122. data/vendor/prelude-ls/src/Num.ls +83 -0
  123. data/vendor/prelude-ls/src/Obj.ls +61 -0
  124. data/vendor/prelude-ls/src/Str.ls +32 -0
  125. data/vendor/prelude-ls/src/index.ls +56 -0
  126. data/vendor/prelude-ls/test/Func.ls +36 -0
  127. data/vendor/prelude-ls/test/List.ls +751 -0
  128. data/vendor/prelude-ls/test/Num.ls +258 -0
  129. data/vendor/prelude-ls/test/Obj.ls +145 -0
  130. data/vendor/prelude-ls/test/Prelude.ls +49 -0
  131. data/vendor/prelude-ls/test/Str.ls +208 -0
  132. data/vendor/prelude-ls/test/browser.html +5 -0
  133. metadata +369 -0
@@ -0,0 +1,178 @@
1
+ /** @jsx React.DOM */
2
+
3
+ (function(conjur, _, React) {
4
+ 'use strict';
5
+
6
+ var knownTypes = ['user', 'group', 'layer', 'host', 'variable', 'policy'];
7
+
8
+ var OwnedResourcesBox = this.OwnedResourcesBox = React.createClass({
9
+ getInitialState: function() {
10
+ return {
11
+ filter: ''
12
+ };
13
+ },
14
+
15
+ handleChange: function(e) {
16
+ e.preventDefault();
17
+
18
+ this.setState({filter: e.target.value});
19
+ },
20
+
21
+ render: function() {
22
+ var filters = [];
23
+
24
+ if (!this.props.resources) {
25
+ return (
26
+ <span>None</span>
27
+ );
28
+ }
29
+
30
+ var items = this.props.resources
31
+ .sort(function(a, b) {
32
+ // TODO: sort by kind with custom weights, by id, by creation date
33
+ // automatically gives sort by kind, id
34
+ return a.id.toLowerCase().localeCompare(b.id.toLowerCase());
35
+ }).map(function(resource) {
36
+ var kind = resource.id.split(':')[1];
37
+ filters.push(kind);
38
+
39
+ if ((this.state.filter === '')
40
+ || (this.state.filter != 'other' && this.state.filter === kind)
41
+ || (this.state.filter === 'other' && _.intersection([kind], knownTypes).length === 0)) {
42
+ return (
43
+ <li className="list-group-item list-group-item-noborder">
44
+ <ResourceLink data={resource} />
45
+ </li>
46
+ );
47
+ }
48
+
49
+ return '';
50
+ }.bind(this));
51
+
52
+ var toggleButton = '';
53
+
54
+ if (!this.props.tabview) {
55
+ toggleButton = (
56
+ <div className="hide-all">
57
+ <a onClick={this.props.handler}>Hide all &laquo;</a>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ var filterSelect = '';
63
+
64
+ if (this.props.resources.length > 9 || this.state.filter != '') {
65
+ // TODO: sort, when done with previous TODO
66
+ filterSelect = _.intersection(_.uniq(filters, true), knownTypes).map(function(f) {
67
+ return (
68
+ <option value={f}>{_.str.capitalize(conjur.utils.pluralize(f))}</option>
69
+ );
70
+ });
71
+
72
+ filterSelect = (
73
+ <div className="form-group row">
74
+ <div className="col-xs-3">
75
+ <select className="form-control" type="select" ref="filter"
76
+ value={this.state.filter} onChange={this.handleChange}>
77
+ <option value="">Show all</option>
78
+ {filterSelect}
79
+ <option value="other">Other</option>
80
+ </select>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <div className="owned">
88
+ {toggleButton}
89
+ {filterSelect}
90
+ <ul className="list-group">{items}</ul>
91
+ </div>
92
+ );
93
+ }
94
+ });
95
+
96
+ var OwnedResourcesSummary = React.createClass({
97
+ render: function() {
98
+ var expand = '';
99
+
100
+ if (this.props.length > 0) {
101
+ expand = (
102
+ <span>
103
+ <br/>
104
+ <span>
105
+ <a onClick={this.props.handler}>Show all &raquo;</a>
106
+ </span>
107
+ </span>
108
+ );
109
+ }
110
+
111
+ var message = '';
112
+
113
+ if (this.props.length === 0) {
114
+ message = 'none';
115
+ } else {
116
+ message = '' + this.props.length + ' things';
117
+ }
118
+
119
+ return (
120
+ <div>
121
+ <span>
122
+ {message}
123
+ </span>
124
+ {expand}
125
+ </div>
126
+ );
127
+ }
128
+ });
129
+
130
+ var OwnedResources = this.OwnedResources = React.createClass({
131
+ getInitialState: function() {
132
+ return {
133
+ expanded: false
134
+ };
135
+ },
136
+
137
+ toggle: function(e) {
138
+ e.preventDefault();
139
+
140
+ this.setState({expanded: !this.state.expanded});
141
+ },
142
+
143
+ render: function() {
144
+ var content = null;
145
+
146
+ if (this.state.expanded || this.props.tabview) {
147
+ content = (
148
+ <OwnedResourcesBox resources={this.props.owned}
149
+ handler={this.toggle}
150
+ tabview={this.props.tabview} />
151
+ );
152
+
153
+ } else {
154
+ content = (
155
+ <OwnedResourcesSummary length={this.props.owned.length}
156
+ handler={this.toggle} />
157
+ );
158
+ }
159
+
160
+ var ownedheader = this.props.tabview ? '': (<h3>Owned assets</h3>);
161
+
162
+ return (
163
+ <section className="owned row">
164
+ {ownedheader}
165
+ <div id="ownedDetails" className="col-xs-6">
166
+ {content}
167
+ </div>
168
+ </section>
169
+ );
170
+ }
171
+ });
172
+
173
+ }).bind(conjur.views)
174
+ (
175
+ conjur,
176
+ _,
177
+ React
178
+ );
@@ -0,0 +1,188 @@
1
+ /** @jsx React.DOM */
2
+
3
+ var PermissionsTable = React.createClass({
4
+ render: function() {
5
+ var rows = [];
6
+ var resources = _.sortBy(this.props.resources, function(r){
7
+ return r.id; // this way it will be sorting by (kind, id)
8
+ });
9
+ resources.forEach(function(r){
10
+ var rowspan = r.permissions.length + 1;
11
+ var parts = r.id.split(":");
12
+ var id = parts[2];
13
+ var kind = parts[1]; // ignore env?
14
+ var cells = [
15
+ <td rowSpan={rowspan}> <ResourceLink id={r.id}/> </td>,
16
+ <td rowSpan={rowspan}> {kind} </td>
17
+ ];
18
+ if(rowspan == 1){
19
+ cells.push(<td colSpan="3"> full permissions </td>);
20
+ }
21
+
22
+ rows.push(
23
+ <tr key={r.id}>{cells}</tr>
24
+ );
25
+
26
+ if(rowspan > 1){
27
+ rows.push(r.permissions.map(function(p){
28
+ return <PermissionRow data={p}/>
29
+ }));
30
+ }
31
+
32
+ });
33
+ rows = _.flatten(rows);
34
+
35
+ var showhidelink = <div><a onClick={this.props.hideHandler}>Hide all &laquo;</a></div>;
36
+
37
+ if (this.props.tabview) {
38
+ showhidelink="";
39
+ }
40
+
41
+ if (rows.length==0) {
42
+ return <span>None</span>;
43
+ }
44
+
45
+ return <div>
46
+ {showhidelink}
47
+ <table>
48
+ <thead>
49
+ <tr>
50
+ <th> Resource </th>
51
+ <th> Kind </th>
52
+ <th> Privilege </th>
53
+ <th> Can Grant? </th>
54
+ <th> Granted By </th>
55
+ </tr>
56
+ </thead>
57
+ <tbody> {rows} </tbody>
58
+ </table>
59
+ </div>
60
+ }
61
+ });
62
+
63
+
64
+ // Renders a permission as a tr
65
+ var PermissionRow = React.createClass({
66
+ render: function(){
67
+ var p = this.props.data;
68
+ return <tr>
69
+ <td> { p.privilege } </td>
70
+ <td> { p.grant_option ? "yes" : "no" }</td>
71
+ <td> <RoleLink id={p.grantor}/> </td>
72
+ </tr>;
73
+ }
74
+ });
75
+
76
+ var PermissionsSummary = React.createClass({
77
+ render: function() {
78
+ var expand = "";
79
+ if ( this.props.length > 0 )
80
+ expand = <span>
81
+ <br/>
82
+ <span>
83
+ <a onClick={this.props.expandHandler}>Show all &raquo;</a>
84
+ </span>
85
+ </span>;
86
+
87
+ return <div>
88
+ <span>
89
+ {this.props.length} permissions
90
+ </span>
91
+ {expand}
92
+ </div>
93
+ }
94
+ });
95
+
96
+ var Permissions = React.createClass({
97
+ getInitialState: function() {
98
+ return {resources: null, loaded: false, expand: false}
99
+ },
100
+
101
+ expand: function(event) {
102
+ this.setState({expand: true})
103
+ },
104
+
105
+ collapse: function(event) {
106
+ this.setState({expand: false})
107
+ },
108
+
109
+ componentWillMount: function() { // shouldn't it be done in model??? otherwise it will re-load stuff on each re-rendering of parent block
110
+ if ( this.props.roles ) {
111
+ $.get(this.url(), function(data){
112
+ this.setPermissions(data);
113
+ }.bind(this));
114
+ }
115
+ },
116
+
117
+ setPermissions: function(_resources) {
118
+ /**
119
+ * Filter out owned resources.
120
+ * Filter out permissions on resources which are not held by the current role.
121
+ */
122
+ var resources;
123
+
124
+ if ( this.props.owned ) {
125
+ var ownedIds = _.pluck(this.props.owned, 'id');
126
+ resources = _resources.filter(function(r) {
127
+ return !_.contains(ownedIds, r.id);
128
+ });
129
+ }
130
+ else
131
+ resources = _resources;
132
+
133
+ var roleSet = {};
134
+ this.props.roles.forEach(function(r) {
135
+ roleSet[r] = r;
136
+ });
137
+
138
+ var cleanResources = resources.filter(function(item) {
139
+ return item.id.split(':')[1] !== "secret"; // assets of 'secret' kind are internal
140
+ });
141
+
142
+ cleanResources.forEach(function(resource) {
143
+ var permissions = resource.permissions;
144
+ var hasPermissions = [];
145
+ permissions.forEach(function(permission) {
146
+ if ( roleSet.hasOwnProperty(permission['role']) )
147
+ hasPermissions.push(permission);
148
+ });
149
+ resource.permissions = hasPermissions;
150
+ });
151
+
152
+ this.setState({resources: cleanResources, loaded: true});
153
+ },
154
+
155
+ render: function(){
156
+ var content;
157
+ if (this.state.loaded) {
158
+ if ( this.state.expand || this.props.tabview )
159
+ content = <PermissionsTable resources={this.state.resources} hideHandler={this.collapse} tabview={this.props.tabview}/>
160
+ else
161
+ content = <PermissionsSummary length={this.state.resources.length} expandHandler={this.expand} />
162
+ }
163
+ else {
164
+ if ( this.props.roles )
165
+ content = <span>Loading...</span>
166
+ else
167
+ content = "You are not authorized to see these permissions";
168
+ }
169
+
170
+ var cx = React.addons.classSet;
171
+ var classes = cx({
172
+ permissions: true,
173
+ loading: !this.state.loaded
174
+ });
175
+
176
+ var permissionsheader = this.props.tabview ? "" : <h3>Permissions held</h3>;
177
+ return <section className={classes}>
178
+ {permissionsheader}
179
+ <div id="permissionDetails">
180
+ {content}
181
+ </div>
182
+ </section>;
183
+ },
184
+
185
+ url: function(){
186
+ return "/api/authz/" + conjur.app.configuration.account + "/resources?acting_as=" + this.props.role;
187
+ }
188
+ })
@@ -0,0 +1,28 @@
1
+ /** @jsx React.DOM */
2
+
3
+ (function(conjur, React) {
4
+ 'use strict';
5
+
6
+ var GenericList = conjur.views.GenericList;
7
+
8
+ var PolicyBox = this.PolicyBox = React.createClass({
9
+ /*getInitialState: function() {
10
+ return { currentNamespace: "", members: [] };
11
+ },*/
12
+ render: function() {
13
+ return (
14
+ <div className="policyBox">
15
+ <div className="policyList">
16
+ <h2>Policies</h2>
17
+ <GenericList data={{kind: 'policies', members: this.props.data.members}} />
18
+ </div>
19
+ </div>
20
+ );
21
+ }
22
+ });
23
+
24
+ }).bind(conjur.views)
25
+ (
26
+ conjur,
27
+ React
28
+ );
@@ -0,0 +1,43 @@
1
+ /** @jsx React.DOM */
2
+
3
+ var Policy = React.createClass({
4
+ mixins: [conjur.views.mixins.Tab],
5
+ render: function() {
6
+ var policy = this.props.data.policy;
7
+ var id = policy.id.split(':')[2];
8
+
9
+ //TODO: policy loader
10
+ //TODO in CLI: why not save text of policy as an annotation?
11
+ var overview_tab =
12
+ <TabPane key="overview" tab="Overview">
13
+ <dl className="dl-horizontal">
14
+ <dt>Owner</dt>
15
+ <dd><RoleLink id={policy.id}/></dd>
16
+ </dl>
17
+ </TabPane>;
18
+
19
+ var permissions_tab = this.permissions_tab(policy.id);
20
+ var memberships_tab = this.memberships_tab(policy.id);
21
+ var annotations_tab = this.annotations_tab();
22
+ var owned_tab = this.owned_tab();
23
+
24
+ var audit_tab =
25
+ <TabPane key="audit" tab="Recent Activity">
26
+ <div className="audit auditGroup">
27
+ <AuditBox roles={[policy.id]} tabview={true} />
28
+ </div>
29
+ </TabPane>;
30
+ var tabs = _.compact( [ overview_tab, owned_tab, memberships_tab, permissions_tab,
31
+ annotations_tab, audit_tab
32
+ ] );
33
+
34
+ return (
35
+ <div className="policy">
36
+ <h2>Policy {id}</h2>
37
+ <TabbedArea defaultActiveKey="overview">
38
+ {tabs}
39
+ </TabbedArea>
40
+ </div>
41
+ );
42
+ }
43
+ });
@@ -0,0 +1,39 @@
1
+ /** @jsx React.DOM */
2
+
3
+ /**
4
+ Renders a link to the resource with id given by this.props.data.
5
+
6
+ Includes a slick little icon for the following kinds:
7
+ TODO which kinds?
8
+ **/
9
+ var ResourceLink = React.createClass({
10
+ render: function(){
11
+ var resourceId = this.props.id || this.props.data.id || this.props.data;
12
+
13
+ var tokens = resourceId.split(':');
14
+ var kind = tokens[1];
15
+ var id = tokens[tokens.length - 1];
16
+ var text = this.props.text || id;
17
+
18
+ var known_types=['user','group','layer','host','variable','policy'];
19
+ var resource_is_known = _.contains(known_types, kind);
20
+
21
+ // we shouldn't build links to for unsupported resources
22
+ var href = "/ui/" + conjur.utils.pluralize(kind) + "/" + encodeURIComponent(id);
23
+ var classes = [ 'resource-link' ];
24
+ if( !this.props.noIcon ) {
25
+ if (resource_is_known) {
26
+ classes.push(kind);
27
+ } else {
28
+ classes.push('abstract');
29
+ if (text==id) {
30
+ text=[kind,text].join(":"); // prepend kind to id
31
+ }
32
+ }
33
+ } else if (text==id) {
34
+ text=[kind,text].join(":"); // prepend kind to id
35
+ }
36
+
37
+ return <a className={classes.join(' ')} href={href}>{text}</a>
38
+ }
39
+ });
@@ -0,0 +1,41 @@
1
+ /** @jsx React.DOM */
2
+
3
+ /** render a link to the role represented by this.props.id
4
+ Example: <RoleLink id="ci:user:jon"/>
5
+ */
6
+ var RoleLink = React.createClass({
7
+ render: function() {
8
+ var tokens = this.props.id.split(":");
9
+ var kind = tokens[1];
10
+ var id = tokens[tokens.length-1];
11
+ if (tokens.length==1) { // just username
12
+ kind="user";
13
+ id=tokens[0];
14
+ }
15
+
16
+ // TODO: shouldn't point to unknown types
17
+ var href = "/ui/" + conjur.utils.pluralize(kind) + "/" + encodeURIComponent(id);
18
+ var classes = ["role-link"];
19
+
20
+ var known_types=['user','group','layer','host','policy'];
21
+ var kind_is_known = _.contains(known_types, kind);
22
+ var text = id;
23
+
24
+ if( !this.props.noIcon ) {
25
+ if (kind_is_known) {
26
+ classes.push(kind);
27
+ } else {
28
+ classes.push('abstract'); // we have no picture for abstract role yet
29
+ if (text==id) {
30
+ text=[kind,text].join(":"); // prepend kind to id
31
+ }
32
+ }
33
+ } else if (text==id) {
34
+ text=[kind,text].join(":"); // prepend kind to id
35
+ }
36
+
37
+ return <a className={classes.join(' ')} href={href}>
38
+ {text}
39
+ </a>;
40
+ }
41
+ });
@@ -0,0 +1,145 @@
1
+ /**@jsx React.DOM*/
2
+
3
+ var SearchGroupTitle = React.createClass({
4
+ render: function(){
5
+ return <span>{this.title()}</span>
6
+ },
7
+
8
+ title: function(){
9
+ var words = this.props.data.kind.replace(/[-_]/, ' ').split(' ');
10
+ words[words.length - 1] = conjur.utils.pluralize(words[words.length - 1]);
11
+ return words.map(_.str.capitalize).join(' ') + ' (' + this.props.data.items.length + ')';
12
+ }
13
+ });
14
+
15
+ var SearchGroupHeading = React.createClass({
16
+ render: function(){
17
+ var targetId = "#search-collapse-" + this.props.data.kind;
18
+ return <div className="panel-heading">
19
+ <h4 className="panel-title">
20
+ <a data-toggle="collapse" data-target={targetId}
21
+ className={'group-heading' + this.props.data.kind}>
22
+ <SearchGroupTitle data={this.props.data} />
23
+ </a>
24
+ </h4>
25
+ </div>;
26
+ },
27
+
28
+ title: function(){
29
+ var words = this.props.data.kind.replace(/[-_]/, ' ').split(' ');
30
+ words[words.length - 1] = conjur.utils.pluralize(words[words.length - 1]);
31
+ return words.map(_.str.capitalize).join(' ') + ' (' + this.props.data.items.length + ')';
32
+ }
33
+ });
34
+
35
+ var SearchResultItem = React.createClass({
36
+ render: function(){
37
+ return <div className="item">
38
+ <h4> { this.titleLink() } </h4>
39
+ <div className="details">
40
+ <strong> ID: </strong> <ResourceLink data={this.props.data.id} noIcon="true"/>
41
+ <strong> Owner: </strong> <RoleLink id={this.props.data.owner} noIcon="true"/>
42
+ </div>
43
+ <div className="comment">
44
+ {this.commentText()}
45
+ </div>
46
+ </div>;
47
+ },
48
+
49
+ commentText: function(){
50
+ var annots = this.annotationsMap();
51
+ return annots['description'] || "";
52
+ },
53
+
54
+ titleLink: function(){
55
+ var annots = this.annotationsMap();
56
+ return <ResourceLink data={this.props.data.id} text={annots['name']}/>
57
+ },
58
+
59
+ annotationsMap: function(){
60
+ if(this._annotationsMap) return this._annotationsMap;
61
+ var map = this._annotationsMap = {};
62
+ (this.props.data.annotations || []).forEach(function(a){
63
+ map[a.name] = a.value;
64
+ });
65
+ return map;
66
+ }
67
+ });
68
+
69
+ // accepts props like data: { kind:"", items:[] }
70
+ var SearchGroup = React.createClass({
71
+ render: function(){
72
+ var id = "search-group-" + this.props.data.kind;
73
+ var items = this.props.data.items.map(function(r){
74
+ return <SearchResultItem data={r}/>
75
+ })
76
+ return <div id={id} className="panel panel-default search-group">
77
+ <SearchGroupHeading data={this.props.data}/>
78
+ <div id={"search-collapse-" + this.props.data.kind} className="panel-collapse collapse in">
79
+ <div className="panel-body">
80
+ {items}
81
+ </div>
82
+ </div>
83
+ </div>;
84
+ }
85
+ });
86
+
87
+ var SearchResults = React.createClass({
88
+ render: function() {
89
+ var results = this.props.data.results;
90
+ var grouped = _.groupBy(results, function(r){
91
+ return r.id.split(':')[1];
92
+ });
93
+ // Don't care about these
94
+ delete grouped['environment-variables']
95
+ delete grouped['notification']
96
+ delete grouped['queue']
97
+
98
+ var groups = _.map(grouped,function(items, key){
99
+ var data = {items:items, kind: key}; // - prevent editor barfing
100
+ return <SearchGroup data={data}/>
101
+ });
102
+ var scores = {
103
+ 'policy': 1,
104
+ 'layer': 2,
105
+ 'group': 3,
106
+ 'host': 4,
107
+ 'user': 5,
108
+ 'variable': 6,
109
+ 'key_pair': 7
110
+ };
111
+ groups.sort(function(a, b) {
112
+ return ( scores[a.props.data.kind] || 100 ) - ( scores[b.props.data.kind] || 100 );
113
+ });
114
+ var toc = groups.map(function(g) {
115
+ var gid = "#search-group-" + g.props.data.kind;
116
+ return <div className="toc-item">
117
+ <a href={gid}><SearchGroupTitle data={g.props.data} /></a>
118
+ </div>
119
+ });
120
+ var heading = "Found " + this.props.data.results.length +
121
+ " resources matching \"" + this.props.data.search + "\"";
122
+ return (
123
+ <div id="searchResults">
124
+ <div className="searchResults">
125
+ <h3> { heading } </h3>
126
+ <div className="search-results-toc">
127
+ {toc}
128
+ </div>
129
+ <div className="search-results">
130
+ {groups}
131
+ </div>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
136
+ });
137
+
138
+ SearchResults.search = function(search, container){
139
+ container = container || document.getElementById('content');
140
+ $.get(conjur.app.endpoints.authz("resources", {search: search.replace('-',' ')}), function(results){
141
+
142
+ var data = {search: search, results: results};
143
+ React.renderComponent(<SearchResults data={data}/>, container);
144
+ });
145
+ }
@@ -0,0 +1,14 @@
1
+ /** @jsx React.DOM */
2
+
3
+ // render a <time> tag. props.timestamp should be a timestamp
4
+ // that moment can parse, props.format should be one of the
5
+ // format strings accepted by moment (optional).
6
+ var Time = React.createClass({
7
+ render: function(){
8
+ var timestamp = this.props.timestamp;
9
+ var format = this.props.format || 'lll';
10
+ var m = moment(timestamp);
11
+
12
+ return <time dateTime={m.format()}>{m.format(format)}</time>;
13
+ }
14
+ })