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.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -3
  3. data/CHANGELOG.md +10 -0
  4. data/Makefile +19 -0
  5. data/README.md +0 -3
  6. data/Rakefile +22 -17
  7. data/TODO.md +0 -23
  8. data/app/.csscomb.json +304 -0
  9. data/app/.eslintignore +3 -0
  10. data/app/.eslintrc +265 -0
  11. data/app/config/preprocessor.js +19 -0
  12. data/app/config/webpack.js +124 -0
  13. data/app/gulpfile.js +96 -0
  14. data/app/package.json +86 -0
  15. data/app/src/actions.js +550 -0
  16. data/app/src/app.js +83 -0
  17. data/app/src/clients/audit.js +34 -0
  18. data/app/src/clients/auth.js +24 -0
  19. data/app/src/clients/generic.js +52 -0
  20. data/app/src/clients/graph.js +7 -0
  21. data/app/src/clients/layer_members.js +18 -0
  22. data/app/src/clients/list.js +31 -0
  23. data/app/src/clients/members.js +20 -0
  24. data/app/src/clients/request.js +531 -0
  25. data/app/src/clients/search.js +5 -0
  26. data/app/src/components/app/__tests__/app-test.js +22 -0
  27. data/app/src/components/app/app.js +36 -0
  28. data/app/src/components/app/wrapper.js +17 -0
  29. data/app/src/components/audit/__tests__/table_header-test.js +22 -0
  30. data/app/src/components/audit/box.js +9 -0
  31. data/app/src/components/audit/constants.js +5 -0
  32. data/app/src/components/audit/entry.js +105 -0
  33. data/app/src/components/audit/fields_mixin.js +11 -0
  34. data/app/src/components/audit/humanize_event.js +213 -0
  35. data/app/src/components/audit/table.js +64 -0
  36. data/app/src/components/audit/table_header.js +37 -0
  37. data/app/src/components/audit/timestamp.js +28 -0
  38. data/app/src/components/auth/login.js +177 -0
  39. data/app/src/components/auth/login.less +71 -0
  40. data/app/src/components/auth/logout.js +42 -0
  41. data/app/src/components/auth/logout.less +21 -0
  42. data/app/src/components/chart/chart.js +540 -0
  43. data/app/src/components/chart/chart_helper_mixin.js +78 -0
  44. data/app/src/components/custom/list.js +3 -0
  45. data/app/src/components/custom/view.js +81 -0
  46. data/app/src/components/dashboard/activity.js +144 -0
  47. data/app/src/components/dashboard/dashboard.js +46 -0
  48. data/app/src/components/flash/flash.js +98 -0
  49. data/app/src/components/flash/flash.less +3 -0
  50. data/app/src/components/generic/__tests__/time-test.js +42 -0
  51. data/app/src/components/generic/annotations.js +39 -0
  52. data/app/src/components/generic/breadcrumbs.js +57 -0
  53. data/app/src/components/generic/foldable_audit_section.js +204 -0
  54. data/app/src/components/generic/list.js +141 -0
  55. data/app/src/components/generic/list_factory.js +41 -0
  56. data/app/src/components/generic/resource_link.js +64 -0
  57. data/app/src/components/generic/role_link.js +66 -0
  58. data/app/src/components/generic/tab_mixin.js +146 -0
  59. data/app/src/components/generic/time.js +32 -0
  60. data/app/src/components/graph/__tests__/collapse-test.js +133 -0
  61. data/app/src/components/graph/__tests__/edges-from-vertices-test.js +48 -0
  62. data/app/src/components/graph/__tests__/new-vertex-set-test.js +16 -0
  63. data/app/src/components/graph/__tests__/next-id-test.js +27 -0
  64. data/app/src/components/graph/__tests__/role-kind-from-id-test.js +24 -0
  65. data/app/src/components/graph/__tests__/vertices-from-edges-test.js +72 -0
  66. data/app/src/components/graph/graph.js +449 -0
  67. data/app/src/components/graph/graph.less +39 -0
  68. data/app/src/components/graph/helpers.js +368 -0
  69. data/app/src/components/group/list.js +3 -0
  70. data/app/src/components/group/view.js +153 -0
  71. data/app/src/components/host/activity.js +111 -0
  72. data/app/src/components/host/details.js +28 -0
  73. data/app/src/components/host/executors.js +77 -0
  74. data/app/src/components/host/host_link.js +18 -0
  75. data/app/src/components/host/list.js +3 -0
  76. data/app/src/components/host/updaters.js +77 -0
  77. data/app/src/components/host/view.js +145 -0
  78. data/app/src/components/layer/list.js +3 -0
  79. data/app/src/components/layer/view.js +197 -0
  80. data/app/src/components/navbar/__tests__/navbar-test.js +21 -0
  81. data/app/src/components/navbar/nav_search_form.js +40 -0
  82. data/app/src/components/navbar/navbar.js +96 -0
  83. data/app/src/components/notfound/notfound.js +35 -0
  84. data/app/src/components/notfound/notfound.less +21 -0
  85. data/app/src/components/owned_resources/owned_resources.js +84 -0
  86. data/app/src/components/owned_resources/owned_resources_box.js +101 -0
  87. data/app/src/components/permissions/permissions.js +138 -0
  88. data/app/src/components/permissions/permissions_table.js +101 -0
  89. data/app/src/components/policy/list.js +3 -0
  90. data/app/src/components/policy/view.js +107 -0
  91. data/app/src/components/refresh/refresh.js +29 -0
  92. data/app/src/components/refresh/refresh.less +15 -0
  93. data/app/src/components/search/group.js +43 -0
  94. data/app/src/components/search/group_heading.js +50 -0
  95. data/app/src/components/search/group_title.js +37 -0
  96. data/app/src/components/search/result_item.js +55 -0
  97. data/app/src/components/search/search.js +118 -0
  98. data/app/src/components/user/activity.js +112 -0
  99. data/app/src/components/user/details.js +30 -0
  100. data/app/src/components/user/list.js +3 -0
  101. data/app/src/components/user/pubkeys.js +118 -0
  102. data/app/src/components/user/pubkeys.less +56 -0
  103. data/app/src/components/user/view.js +143 -0
  104. data/app/src/components/variable/activity.js +101 -0
  105. data/app/src/components/variable/details.js +46 -0
  106. data/app/src/components/variable/fetchers.js +77 -0
  107. data/app/src/components/variable/list.js +3 -0
  108. data/app/src/components/variable/updaters.js +77 -0
  109. data/app/src/components/variable/view.js +115 -0
  110. data/app/src/constants.js +36 -0
  111. data/{public → app/src}/images/conjur-logo.svg +0 -0
  112. data/{public → app/src}/images/icon-client-pc.svg +0 -0
  113. data/{public → app/src}/images/icon-environment.png +0 -0
  114. data/{public → app/src}/images/icon-person.svg +0 -0
  115. data/{public → app/src}/images/icon-policy.png +0 -0
  116. data/{public → app/src}/images/icon-resource.png +0 -0
  117. data/{public → app/src}/images/icon-service-dots.svg +0 -0
  118. data/{public → app/src}/images/icon-variable.png +0 -0
  119. data/app/src/pages/index.html +27 -0
  120. data/app/src/routes.js +64 -0
  121. data/app/src/stores/app_store.js +35 -0
  122. data/app/src/stores/audit_store.js +143 -0
  123. data/app/src/stores/graph_store.js +51 -0
  124. data/app/src/stores/group_store.js +104 -0
  125. data/app/src/stores/host_store.js +111 -0
  126. data/app/src/stores/layer_store.js +115 -0
  127. data/app/src/stores/policy_store.js +88 -0
  128. data/app/src/stores/resources_store.js +115 -0
  129. data/app/src/stores/route_store.js +21 -0
  130. data/app/src/stores/search_store.js +77 -0
  131. data/app/src/stores/user_store.js +109 -0
  132. data/app/src/stores/variable_store.js +93 -0
  133. data/app/src/styles/bootstrap.less +54 -0
  134. data/{public/css → app/src/styles}/styles.less +26 -82
  135. data/app/src/utils.js +38 -0
  136. data/app/src/vendor/pace.js +2 -0
  137. data/conjur-asset-ui.gemspec +3 -4
  138. data/docker/assets-build/Dockerfile +12 -0
  139. data/docker/conjur-ui/Dockerfile +33 -0
  140. data/docker/conjur-ui/README.md +38 -0
  141. data/docker/conjur-ui/mime.types +90 -0
  142. data/docker/conjur-ui/nginx.conf +110 -0
  143. data/docker/conjur-ui/start.py +72 -0
  144. data/docker/conjur-ui/start.sh +18 -0
  145. data/docker/conjur-ui/test.env +8 -0
  146. data/lib/conjur-asset-ui-version.rb +1 -1
  147. data/lib/conjur/command/ui.rb +10 -2
  148. data/lib/conjur/webserver/home.rb +3 -3
  149. data/lib/conjur/webserver/login.rb +1 -1
  150. data/lib/conjur/webserver/server.rb +16 -4
  151. data/public/js/views/roleGraph.js +91 -0
  152. metadata +167 -105
  153. data/.jshintrc +0 -41
  154. data/bower.json +0 -98
  155. data/gulpfile.js +0 -139
  156. data/package.json +0 -47
  157. data/preprocessor.js +0 -7
  158. data/public/_client_libs.html +0 -9
  159. data/public/index.html.erb +0 -63
  160. data/public/js/init.js +0 -196
  161. data/public/js/lib/pace.js +0 -2
  162. data/public/js/lib/sorted-set.no-require.js +0 -1145
  163. data/public/js/lib/sorted-set.no-require.js.txt +0 -6
  164. data/public/js/models/groupRecord.js +0 -72
  165. data/public/js/models/hostRecord.js +0 -60
  166. data/public/js/models/layerRecord.js +0 -79
  167. data/public/js/models/namespace.js +0 -12
  168. data/public/js/models/policyList.js +0 -16
  169. data/public/js/models/policyRecord.js +0 -54
  170. data/public/js/models/record.js +0 -117
  171. data/public/js/models/resourceList.js +0 -87
  172. data/public/js/models/userList.js +0 -25
  173. data/public/js/models/userRecord.js +0 -75
  174. data/public/js/models/variableList.js +0 -27
  175. data/public/js/models/variableRecord.js +0 -77
  176. data/public/js/routers.js +0 -242
  177. data/public/js/views/annotations.js +0 -47
  178. data/public/js/views/audit.js +0 -369
  179. data/public/js/views/breadcrumbs.js +0 -62
  180. data/public/js/views/chart.js +0 -617
  181. data/public/js/views/dashboard.js +0 -146
  182. data/public/js/views/generic.js +0 -122
  183. data/public/js/views/group.js +0 -109
  184. data/public/js/views/groups.js +0 -26
  185. data/public/js/views/host.js +0 -200
  186. data/public/js/views/hosts.js +0 -26
  187. data/public/js/views/layer.js +0 -146
  188. data/public/js/views/layers.js +0 -26
  189. data/public/js/views/mixins/search.js +0 -22
  190. data/public/js/views/mixins/tabs.js +0 -154
  191. data/public/js/views/namespaces.js +0 -40
  192. data/public/js/views/navSearch.js +0 -36
  193. data/public/js/views/owned.js +0 -184
  194. data/public/js/views/permissions.js +0 -254
  195. data/public/js/views/policies.js +0 -26
  196. data/public/js/views/policy.js +0 -70
  197. data/public/js/views/resource.js +0 -59
  198. data/public/js/views/role.js +0 -63
  199. data/public/js/views/searchResults.js +0 -212
  200. data/public/js/views/sections.js +0 -226
  201. data/public/js/views/time.js +0 -39
  202. data/public/js/views/user.js +0 -297
  203. data/public/js/views/users.js +0 -26
  204. data/public/js/views/variable.js +0 -310
  205. data/public/js/views/variables.js +0 -26
  206. data/spec/javascripts/helpers/.gitkeep +0 -0
  207. data/spec/javascripts/support/jasmine.yml +0 -112
  208. data/spec/javascripts/support/jasmine_helper.rb +0 -22
  209. data/spec/javascripts/support/run.html.erb +0 -23
  210. data/spec/javascripts/views/AuditSpec.js +0 -22
  211. data/spec/javascripts/views/AuditSpec.ls +0 -18
@@ -0,0 +1,3 @@
1
+ var genericListFactory = require('../generic/list_factory');
2
+
3
+ export default genericListFactory('layer', 'Layers');
@@ -0,0 +1,197 @@
1
+ var React = require('react'),
2
+ Fluxxor = require('fluxxor');
3
+
4
+ import {compact} from 'lodash';
5
+
6
+ var FluxMixin = Fluxxor.FluxMixin(React),
7
+ StoreWatchMixin = Fluxxor.StoreWatchMixin;
8
+
9
+ var TabbedArea = require('react-bootstrap/lib/TabbedArea'),
10
+ TabPane = require('react-bootstrap/lib/TabPane');
11
+
12
+ var AuditTable = require('../audit/table'),
13
+ Breadcrumbs = require('../generic/breadcrumbs'),
14
+ RoleLink = require('../generic/role_link'),
15
+ TabMixin = require('../generic/tab_mixin'),
16
+ HostLink = require('../host/host_link'),
17
+ RoleGraph = require('../graph/graph');
18
+
19
+ import {getTabname} from '../../utils';
20
+
21
+ var abstractRole = function(expected) {
22
+ return function(role) {
23
+ var tokens = role.split(':'),
24
+ kind = tokens[1],
25
+ abstractKinds = ['@', 'layer'],
26
+ isAbstract = abstractKinds.indexOf(kind) !== -1;
27
+
28
+ return isAbstract === expected;
29
+ };
30
+ };
31
+
32
+ export default React.createClass({
33
+ displayName: 'LayerView',
34
+
35
+ mixins: [FluxMixin, StoreWatchMixin('audit', 'layer', 'resources', 'route'), TabMixin],
36
+
37
+ askForNewData(state) {
38
+ var actions = this.getFlux().actions;
39
+
40
+ actions.audit.loadForRole('layer', state.id);
41
+ actions.audit.loadForResource('layer', state.id);
42
+ actions.layer.load(state.id);
43
+ actions.resources.loadOne('layer', state.id);
44
+ },
45
+
46
+ componentDidMount() {
47
+ this.askForNewData(this.state);
48
+ },
49
+
50
+ componentWillUpdate(nextProps, nextState) {
51
+ if (this.state.id !== nextState.id) {
52
+ this.askForNewData(nextState);
53
+ }
54
+
55
+ return true;
56
+ },
57
+
58
+ getStateFromFlux() {
59
+ var flux = this.getFlux(),
60
+ id = flux.store('route').getParam('id'),
61
+ audit = flux.store('audit').getEventFor('layer', id),
62
+ data = flux.store('layer').getData(),
63
+ resource = flux.store('resources').getResource('layer', id);
64
+
65
+ var ret = {
66
+ id: id,
67
+ loading: data.loading,
68
+ layer: data.layer,
69
+ owned: data.owned,
70
+ roles: data.roles,
71
+ owner: resource.data.owner,
72
+ annotations: resource.data.annotations,
73
+ resource: resource.data,
74
+ users: data.users,
75
+ admins: data.admins,
76
+ resources: data.resources,
77
+ audit: audit.data
78
+ };
79
+
80
+ ret.loading.audit = audit.loading;
81
+ ret.loading.owner = resource.loading;
82
+ ret.loading.annotations = resource.loading;
83
+
84
+ return ret;
85
+ },
86
+
87
+ render() {
88
+ var hosts = this.state.layer.hosts.map(function(host) {
89
+ return (
90
+ <li className="list-group-item list-group-item-noborder">
91
+ <HostLink data={host} />
92
+ </li>
93
+ );
94
+ });
95
+
96
+ var admins = this.state.admins.filter(abstractRole(false)).map(function(role) {
97
+ return (
98
+ <li className="list-group-item list-group-item-noborder">
99
+ <RoleLink id={role}/>
100
+ </li>
101
+ );
102
+ });
103
+
104
+ var users = this.state.users.filter(abstractRole(false)).map(function(role) {
105
+ return (
106
+ <li className="list-group-item list-group-item-noborder">
107
+ <RoleLink id={role}/>
108
+ </li>
109
+ );
110
+ });
111
+
112
+ // TODO: controls to add/remove admins/users/hosts
113
+ var overviewTab = (
114
+ <TabPane eventKey="overview" tab="Overview">
115
+ <dl className="dl-horizontal">
116
+ <dt>Owner</dt>
117
+ <dd><RoleLink id={this.state.owner} /></dd>
118
+
119
+ <dt>Created by</dt>
120
+ <dd><RoleLink id={this.state.layer.userid} /></dd>
121
+ </dl>
122
+ </TabPane>
123
+ );
124
+
125
+ var adminsTab = (
126
+ <TabPane eventKey="admins" tab={getTabname('Admins', admins)}>
127
+ <ul className="list-group">
128
+ {admins}
129
+ </ul>
130
+ </TabPane>
131
+ );
132
+
133
+ var usersTab = (
134
+ <TabPane eventKey="users" tab={getTabname('Users', users)}>
135
+ <ul className="list-group">
136
+ {users}
137
+ </ul>
138
+ </TabPane>
139
+ );
140
+
141
+ var hostsTab = (
142
+ <TabPane eventKey="hosts" tab={getTabname('Hosts', hosts)}>
143
+ <ul className="list-group">
144
+ {hosts}
145
+ </ul>
146
+ </TabPane>
147
+ );
148
+
149
+ var auditTab = (
150
+ <TabPane eventKey="audit" tab="Recent Activity">
151
+ <div className="audit auditGroup">
152
+ <AuditTable events={this.state.audit}
153
+ caption=""
154
+ isLoading={this.state.loading.audit} />
155
+ </div>
156
+ </TabPane>
157
+ );
158
+
159
+ var permissionsTab = this.permissionsTab(this.state.layer.roleid),
160
+ membershipsTab = this.membershipsTab(this.state.layer.roleid),
161
+ annotationsTab = this.annotationsTab(),
162
+ ownedTab = this.ownedTab();
163
+
164
+ var tabs = compact([
165
+ overviewTab,
166
+ adminsTab,
167
+ usersTab,
168
+ hostsTab,
169
+ ownedTab,
170
+ membershipsTab,
171
+ permissionsTab,
172
+ annotationsTab,
173
+ auditTab
174
+ ]);
175
+
176
+ var elems = [
177
+ {url: '/ui/layers', text: 'Layers'},
178
+ {url: '', text: this.state.layer.id},
179
+ {url: '', text: '(owned by ' + this.state.layer.ownerid + ')'}
180
+ ];
181
+
182
+ return (
183
+ <div className="b-layer">
184
+ <Breadcrumbs elems={elems} />
185
+ <hr />
186
+ <h2>Layer {this.state.layer.id}</h2>
187
+ <TabbedArea defaultActiveKey="overview">
188
+ {tabs}
189
+ </TabbedArea>
190
+ <hr />
191
+ <RoleGraph height="400"
192
+ kind="layer"
193
+ id={this.state.id} />
194
+ </div>
195
+ );
196
+ }
197
+ });
@@ -0,0 +1,21 @@
1
+ /* global jest, describe, it, expect */
2
+
3
+ 'use strict';
4
+
5
+ jest.dontMock('../navbar');
6
+
7
+ var React = require('react/addons');
8
+ var TestUtils = React.addons.TestUtils;
9
+ var Navbar = require('../navbar');
10
+
11
+ describe('app', () => {
12
+ it('default render', () => {
13
+ var component = TestUtils.renderIntoDocument(
14
+ <Navbar userId="userId" />
15
+ );
16
+
17
+ var element = TestUtils.findRenderedDOMComponentWithClass(component, 'container');
18
+
19
+ expect(element.getDOMNode()).toBe(true);
20
+ });
21
+ });
@@ -0,0 +1,40 @@
1
+ var React = require('react'),
2
+ Router = require('react-router'),
3
+ Fluxxor = require('fluxxor');
4
+
5
+ var FluxMixin = Fluxxor.FluxMixin(React);
6
+
7
+ export default React.createClass({
8
+ displayName: 'NavSearchForm',
9
+
10
+ mixins: [FluxMixin, Router.Navigation],
11
+
12
+ render() {
13
+ return (
14
+ <form className="form-inline navbar-form"
15
+ role="search"
16
+ onSubmit={this.handleSubmit}>
17
+ <div className="form-group">
18
+ <input ref="input"
19
+ type="text"
20
+ className="form-control"
21
+ placeholder="Search Conjur"></input>
22
+ </div>
23
+ <button type="submit"
24
+ className="btn btn-default search-button">
25
+ Search
26
+ </button>
27
+ </form>
28
+ );
29
+ },
30
+
31
+ handleSubmit() {
32
+ var search = this.refs.input.getDOMNode().value;
33
+
34
+ if (search && search.length !== 0) {
35
+ this.transitionTo('search', {query: window.encodeURIComponent(search)});
36
+ }
37
+
38
+ return false;
39
+ }
40
+ });
@@ -0,0 +1,96 @@
1
+ var React = require('react'),
2
+ Fluxxor = require('fluxxor');
3
+
4
+ import {DropdownButton, Navbar, Nav, MenuItem} from 'react-bootstrap';
5
+ import {SplitButton, Button} from 'react-bootstrap';
6
+
7
+ var NavItemLink = require('react-router-bootstrap/lib/NavItemLink'),
8
+ MenuItemLink = require('react-router-bootstrap/lib/MenuItemLink');
9
+
10
+ var NavSearchForm = require('./nav_search_form');
11
+
12
+ var FluxMixin = Fluxxor.FluxMixin(React);
13
+
14
+ export default React.createClass({
15
+ displayName: 'Navbar',
16
+
17
+ mixins: [FluxMixin],
18
+
19
+ contextTypes: {
20
+ router: React.PropTypes.func
21
+ },
22
+
23
+ handleClick(e) {
24
+ e.preventDefault();
25
+
26
+ this.context.router.transitionTo('user', {id: this.props.username});
27
+ },
28
+
29
+ render() {
30
+ const style = {marginTop: '8px'},
31
+ listStyle = {marginRight: '15px'};
32
+
33
+ var button = (
34
+ <Button title={`user ${this.props.username}`}
35
+ style={style}
36
+ onClick={this.handleClick}>
37
+ {`user ${this.props.username}`}
38
+ </Button>
39
+ );
40
+
41
+ if (this.getFlux().actions.getRManager().allowLogout) {
42
+ button = (
43
+ <SplitButton title={`user ${this.props.username}`}
44
+ style={style}
45
+ onClick={this.handleClick}>
46
+ <MenuItemLink to="logout">
47
+ Logout
48
+ </MenuItemLink>
49
+ </SplitButton>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <Navbar brand="Conjur">
55
+ <Nav eventKey={0}>
56
+ <NavItemLink to="dashboard">
57
+ Dashboard
58
+ </NavItemLink>
59
+ <DropdownButton eventKey={3} title="Directory">
60
+ <MenuItemLink to="users">
61
+ Users
62
+ </MenuItemLink>
63
+ <MenuItemLink to="groups">
64
+ Groups
65
+ </MenuItemLink>
66
+ <MenuItem divider={true} />
67
+ <MenuItemLink to="hosts">
68
+ Hosts
69
+ </MenuItemLink>
70
+ <MenuItemLink to="layers">
71
+ Layers
72
+ </MenuItemLink>
73
+ <MenuItem divider={true} />
74
+ <MenuItemLink to="variables">
75
+ Variables
76
+ </MenuItemLink>
77
+ <MenuItemLink to="custom-types">
78
+ Custom Types
79
+ </MenuItemLink>
80
+ </DropdownButton>
81
+ <NavItemLink to="policies">
82
+ Policies
83
+ </NavItemLink>
84
+ </Nav>
85
+ <Nav right={true} eventKey={1} style={listStyle}>
86
+ <li>
87
+ {button}
88
+ </li>
89
+ <li>
90
+ <NavSearchForm />
91
+ </li>
92
+ </Nav>
93
+ </Navbar>
94
+ );
95
+ }
96
+ });
@@ -0,0 +1,35 @@
1
+ import React, {Component} from 'react';
2
+ import {Link} from 'react-router';
3
+
4
+ import './notfound.less';
5
+
6
+ export class NotFound extends Component {
7
+ render() {
8
+ return (
9
+ <div className="container-fluid b-notfound">
10
+ <div className="row b-notfound--row">
11
+ <div className="col-md-12">
12
+ <div className="error-template">
13
+ <h1>
14
+ Oops!
15
+ </h1>
16
+ <h2>
17
+ 404 Not Found
18
+ </h2>
19
+ <div className="error-details">
20
+ Sorry, an error has occured, Requested page not found!
21
+ </div>
22
+ <div className="b-notfound--error-actions">
23
+ <Link to="dashboard" className="btn btn-primary btn-lg">
24
+ <span className="glyphicon glyphicon-home"></span> Take me to the dashboard
25
+ </Link>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ );
32
+ }
33
+ }
34
+
35
+ NotFound.displayName = 'NotFound';
@@ -0,0 +1,21 @@
1
+ .b-notfound {
2
+ top: 0;
3
+ left: 0;
4
+ right: 0;
5
+ bottom: 0;
6
+ display: block;
7
+ overflow: auto;
8
+ position: fixed;
9
+ background: #43525A;
10
+ color: white;
11
+ }
12
+
13
+ .b-notfound--row {
14
+ margin: 50px auto auto;
15
+ margin-top: 70px;
16
+ margin-left: 70px !important;
17
+ }
18
+
19
+ .b-notfound--error-actions {
20
+ margin-top: 10px;
21
+ }
@@ -0,0 +1,84 @@
1
+ var React = require('react');
2
+
3
+ var OwnedResourcesBox = require('./owned_resources_box');
4
+
5
+ var OwnedResourcesSummary = React.createClass({
6
+ displayName: 'OwnedResourcesSummary',
7
+
8
+ render() {
9
+ var expand = '';
10
+
11
+ if (this.props.length > 0) {
12
+ expand = (
13
+ <span>
14
+ <br/>
15
+ <span>
16
+ <a onClick={this.props.handler}>Show all &raquo;</a>
17
+ </span>
18
+ </span>
19
+ );
20
+ }
21
+
22
+ var message = '';
23
+
24
+ if (this.props.length === 0) {
25
+ message = 'none';
26
+ } else {
27
+ message = '' + this.props.length + ' things';
28
+ }
29
+
30
+ return (
31
+ <div>
32
+ <span>
33
+ {message}
34
+ </span>
35
+ {expand}
36
+ </div>
37
+ );
38
+ }
39
+ });
40
+
41
+ export default React.createClass({
42
+ displayName: 'OwnedResources',
43
+
44
+ getInitialState() {
45
+ return {
46
+ expanded: false
47
+ };
48
+ },
49
+
50
+ toggle(e) {
51
+ e.preventDefault();
52
+
53
+ this.setState({expanded: !this.state.expanded});
54
+ },
55
+
56
+ render() {
57
+ var content = null;
58
+
59
+ if (this.state.expanded || this.props.tabview) {
60
+ content = (
61
+ <OwnedResourcesBox resources={this.props.owned}
62
+ handler={this.toggle}
63
+ tabview={this.props.tabview} />
64
+ );
65
+
66
+ } else {
67
+ content = (
68
+ <OwnedResourcesSummary length={this.props.owned.length}
69
+ handler={this.toggle} />
70
+ );
71
+ }
72
+
73
+ var ownedheader = this.props.tabview ? '': (<h3>Owned assets</h3>);
74
+
75
+ return (
76
+ <section className="owned row">
77
+ {ownedheader}
78
+ <div id="ownedDetails" className="col-xs-6">
79
+ {content}
80
+ </div>
81
+ </section>
82
+ );
83
+ }
84
+ });