phobos_checkpoint_ui 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +81 -0
  8. data/Rakefile +12 -0
  9. data/assets/index-52cbf3063583f3c09a4b-0.css +2 -0
  10. data/assets/index-52cbf3063583f3c09a4b-0.css.map +1 -0
  11. data/assets/index-52cbf3063583f3c09a4b-1.css +2 -0
  12. data/assets/index-52cbf3063583f3c09a4b-1.css.map +1 -0
  13. data/assets/index-52cbf3063583f3c09a4b.js +49 -0
  14. data/assets/index-52cbf3063583f3c09a4b.js.map +1 -0
  15. data/assets/index.html +12 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/circle.yml +29 -0
  19. data/frontend/.babelrc +3 -0
  20. data/frontend/.editorconfig +8 -0
  21. data/frontend/.eslintignore +2 -0
  22. data/frontend/.eslintrc +3 -0
  23. data/frontend/.npmignore +8 -0
  24. data/frontend/package.json +45 -0
  25. data/frontend/sagui.config.js +52 -0
  26. data/frontend/src/actions/event-details.js +42 -0
  27. data/frontend/src/actions/event-details.spec.js +71 -0
  28. data/frontend/src/actions/event-overview.js +11 -0
  29. data/frontend/src/actions/event-overview.spec.js +20 -0
  30. data/frontend/src/actions/event-retry.js +58 -0
  31. data/frontend/src/actions/event-retry.spec.js +117 -0
  32. data/frontend/src/actions/events-search.js +75 -0
  33. data/frontend/src/actions/events-search.spec.js +197 -0
  34. data/frontend/src/actions/flash-messages.js +16 -0
  35. data/frontend/src/actions/flash-messages.spec.js +23 -0
  36. data/frontend/src/actions/index.js +24 -0
  37. data/frontend/src/actions/search-input-filter.js +14 -0
  38. data/frontend/src/actions/search-input-filter.spec.js +20 -0
  39. data/frontend/src/api.js +46 -0
  40. data/frontend/src/components/event/error-message.js +19 -0
  41. data/frontend/src/components/event/error-message.scss +8 -0
  42. data/frontend/src/components/event/event.scss +3 -0
  43. data/frontend/src/components/event/index.js +77 -0
  44. data/frontend/src/components/event/index.spec.js +89 -0
  45. data/frontend/src/components/event/loading.js +16 -0
  46. data/frontend/src/components/event/style.js +16 -0
  47. data/frontend/src/components/event-overview/attribute.js +24 -0
  48. data/frontend/src/components/event-overview/event-overview.scss +28 -0
  49. data/frontend/src/components/event-overview/index.js +47 -0
  50. data/frontend/src/components/event-overview/index.spec.js +67 -0
  51. data/frontend/src/components/event-overview-dialog/event-overview-dialog.scss +15 -0
  52. data/frontend/src/components/event-overview-dialog/index.js +85 -0
  53. data/frontend/src/components/event-retry-dialog/index.js +72 -0
  54. data/frontend/src/components/events-list/events-list.scss +49 -0
  55. data/frontend/src/components/events-list/index.js +62 -0
  56. data/frontend/src/components/events-list/index.spec.js +59 -0
  57. data/frontend/src/components/flash-message/flash-message.scss +40 -0
  58. data/frontend/src/components/flash-message/index.js +45 -0
  59. data/frontend/src/components/flash-message/index.spec.js +59 -0
  60. data/frontend/src/components/flash-message-list/index.js +34 -0
  61. data/frontend/src/components/header/header.scss +29 -0
  62. data/frontend/src/components/header/index.js +44 -0
  63. data/frontend/src/components/search-input/index.js +104 -0
  64. data/frontend/src/components/search-input/index.spec.js +56 -0
  65. data/frontend/src/components/search-input/search-input.scss +7 -0
  66. data/frontend/src/configs.js +15 -0
  67. data/frontend/src/helpers.spec.js +2 -0
  68. data/frontend/src/index.html +12 -0
  69. data/frontend/src/index.js +26 -0
  70. data/frontend/src/index.scss +31 -0
  71. data/frontend/src/reducers/event-details.js +32 -0
  72. data/frontend/src/reducers/event-details.spec.js +54 -0
  73. data/frontend/src/reducers/events-filters.js +17 -0
  74. data/frontend/src/reducers/events-filters.spec.js +34 -0
  75. data/frontend/src/reducers/events.js +48 -0
  76. data/frontend/src/reducers/events.spec.js +95 -0
  77. data/frontend/src/reducers/flash-messages.js +14 -0
  78. data/frontend/src/reducers/flash-messages.spec.js +34 -0
  79. data/frontend/src/reducers/index.js +18 -0
  80. data/frontend/src/reducers/index.spec.js +20 -0
  81. data/frontend/src/reducers/xhr-status.js +75 -0
  82. data/frontend/src/reducers/xhr-status.spec.js +94 -0
  83. data/frontend/src/routes.js +20 -0
  84. data/frontend/src/store.js +15 -0
  85. data/frontend/src/views/event-details.js +50 -0
  86. data/frontend/src/views/events-search.js +112 -0
  87. data/frontend/src/views/events-search.scss +24 -0
  88. data/frontend/src/views/events-search.spec.js +96 -0
  89. data/frontend/src/views/layout.js +24 -0
  90. data/frontend/src/views/layout.scss +3 -0
  91. data/lib/phobos_checkpoint_ui/app.rb +11 -0
  92. data/lib/phobos_checkpoint_ui/static_app.rb +19 -0
  93. data/lib/phobos_checkpoint_ui/tasks.rb +20 -0
  94. data/lib/phobos_checkpoint_ui/version.rb +3 -0
  95. data/lib/phobos_checkpoint_ui.rb +10 -0
  96. data/phobos_checkpoint_ui.gemspec +53 -0
  97. data/screenshot1.png +0 -0
  98. data/screenshot2.png +0 -0
  99. metadata +267 -0
@@ -0,0 +1,75 @@
1
+ import {
2
+ REQUEST_SEARCH_RESULTS,
3
+ RECEIVE_SEARCH_RESULTS,
4
+ REQUEST_SEARCH_RESULTS_FAILED,
5
+ LOAD_MORE_SEARCH_RESULTS,
6
+ TRIGGER_SEARCH,
7
+ REQUEST_EVENT_RETRY,
8
+ RECEIVE_EVENT_RETRY,
9
+ REQUEST_EVENT_RETRY_FAILED,
10
+ REQUEST_EVENT_DETAILS,
11
+ RECEIVE_EVENT_DETAILS
12
+ } from 'actions'
13
+
14
+ const initialState = {
15
+ isFetchingEvents: false,
16
+ isRetryingEvent: false,
17
+ isFetchingEventDetails: false,
18
+ currentEventsOffset: 0,
19
+ lastEventsLoadSize: 0
20
+ }
21
+
22
+ export default (state = initialState, action) => {
23
+ switch (action.type) {
24
+ case TRIGGER_SEARCH:
25
+ return Object.assign({}, state, {
26
+ currentEventsOffset: 0,
27
+ lastEventsLoadSize: 0
28
+ })
29
+
30
+ case REQUEST_SEARCH_RESULTS:
31
+ return Object.assign({}, state, {
32
+ isFetchingEvents: true
33
+ })
34
+
35
+ case RECEIVE_SEARCH_RESULTS:
36
+ return Object.assign({}, state, {
37
+ isFetchingEvents: false,
38
+ lastEventsLoadSize: action.events.length
39
+ })
40
+
41
+ case REQUEST_SEARCH_RESULTS_FAILED:
42
+ return Object.assign({}, state, {
43
+ isFetchingEvents: false
44
+ })
45
+
46
+ case LOAD_MORE_SEARCH_RESULTS:
47
+ return Object.assign({}, state, {
48
+ currentEventsOffset: action.offset
49
+ })
50
+
51
+ case REQUEST_EVENT_RETRY:
52
+ return Object.assign({}, state, {
53
+ isRetryingEvent: true
54
+ })
55
+
56
+ case RECEIVE_EVENT_RETRY:
57
+ case REQUEST_EVENT_RETRY_FAILED:
58
+ return Object.assign({}, state, {
59
+ isRetryingEvent: false
60
+ })
61
+
62
+ case REQUEST_EVENT_DETAILS:
63
+ return Object.assign({}, state, {
64
+ isFetchingEventDetails: true
65
+ })
66
+
67
+ case RECEIVE_EVENT_DETAILS:
68
+ return Object.assign({}, state, {
69
+ isFetchingEventDetails: false
70
+ })
71
+
72
+ default:
73
+ return state
74
+ }
75
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ TRIGGER_SEARCH,
3
+ REQUEST_SEARCH_RESULTS,
4
+ RECEIVE_SEARCH_RESULTS,
5
+ REQUEST_SEARCH_RESULTS_FAILED,
6
+ LOAD_MORE_SEARCH_RESULTS,
7
+ REQUEST_EVENT_RETRY,
8
+ RECEIVE_EVENT_RETRY,
9
+ REQUEST_EVENT_RETRY_FAILED
10
+ } from 'actions'
11
+
12
+ import reducer from 'reducers/xhr-status'
13
+
14
+ describe('reducers/xhr-status', () => {
15
+ describe('for TRIGGER_SEARCH', () => {
16
+ it('resets currentEventsOffset and lastEventsLoadSize', () => {
17
+ const currentState = { currentEventsOffset: 5, lastEventsLoadSize: 10 }
18
+ const action = { type: TRIGGER_SEARCH }
19
+ const expectedState = { currentEventsOffset: 0, lastEventsLoadSize: 0 }
20
+ expect(reducer(currentState, action)).toEqual(expectedState)
21
+ })
22
+ })
23
+
24
+ describe('for REQUEST_SEARCH_RESULTS', () => {
25
+ it('enables isFetchingEvents', () => {
26
+ const currentState = { isFetchingEvents: false }
27
+ const action = { type: REQUEST_SEARCH_RESULTS }
28
+ const expectedState = { isFetchingEvents: true }
29
+ expect(reducer(currentState, action)).toEqual(expectedState)
30
+ })
31
+ })
32
+
33
+ describe('for RECEIVE_SEARCH_RESULTS', () => {
34
+ it('disables isFetchingEvents and keep the load size', () => {
35
+ const currentState = { isFetchingEvents: true }
36
+ const action = { type: RECEIVE_SEARCH_RESULTS, events: ['A', 'B', 'C'] }
37
+ const expectedState = { isFetchingEvents: false, lastEventsLoadSize: 3 }
38
+ expect(reducer(currentState, action)).toEqual(expectedState)
39
+ })
40
+ })
41
+
42
+ describe('for REQUEST_SEARCH_RESULTS_FAILED', () => {
43
+ it('disables isFetchingEvents', () => {
44
+ const currentState = { isFetchingEvents: true }
45
+ const action = { type: REQUEST_SEARCH_RESULTS_FAILED }
46
+ const expectedState = { isFetchingEvents: false }
47
+ expect(reducer(currentState, action)).toEqual(expectedState)
48
+ })
49
+ })
50
+
51
+ describe('for LOAD_MORE_SEARCH_RESULTS', () => {
52
+ it('updates currentEventsOffset', () => {
53
+ const currentState = { currentEventsOffset: 0 }
54
+ const action = { type: LOAD_MORE_SEARCH_RESULTS, offset: 3 }
55
+ const expectedState = { currentEventsOffset: 3 }
56
+ expect(reducer(currentState, action)).toEqual(expectedState)
57
+ })
58
+ })
59
+
60
+ describe('for REQUEST_EVENT_RETRY', () => {
61
+ it('enables isRetryingEvent', () => {
62
+ const currentState = { isRetryingEvent: false }
63
+ const action = { type: REQUEST_EVENT_RETRY }
64
+ const expectedState = { isRetryingEvent: true }
65
+ expect(reducer(currentState, action)).toEqual(expectedState)
66
+ })
67
+ })
68
+
69
+ describe('for RECEIVE_EVENT_RETRY', () => {
70
+ it('disables isRetryingEvent', () => {
71
+ const currentState = { isRetryingEvent: true }
72
+ const action = { type: RECEIVE_EVENT_RETRY }
73
+ const expectedState = { isRetryingEvent: false }
74
+ expect(reducer(currentState, action)).toEqual(expectedState)
75
+ })
76
+ })
77
+
78
+ describe('for REQUEST_EVENT_RETRY_FAILED', () => {
79
+ it('disables isRetryingEvent', () => {
80
+ const currentState = { isRetryingEvent: true }
81
+ const action = { type: REQUEST_EVENT_RETRY_FAILED }
82
+ const expectedState = { isRetryingEvent: false }
83
+ expect(reducer(currentState, action)).toEqual(expectedState)
84
+ })
85
+ })
86
+
87
+ describe('for default', () => {
88
+ it('returns the currentState', () => {
89
+ const currentState = { current: true }
90
+ const action = { type: 'another' }
91
+ expect(reducer(currentState, action)).toEqual(currentState)
92
+ })
93
+ })
94
+ })
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import { Router, Route, IndexRedirect, browserHistory } from 'react-router'
3
+ import { syncHistoryWithStore } from 'react-router-redux'
4
+
5
+ import store from 'store'
6
+ import Layout from 'views/layout'
7
+ import EventsSearch from 'views/events-search'
8
+ import EventDetails from 'views/event-details'
9
+
10
+ export const history = syncHistoryWithStore(browserHistory, store)
11
+
12
+ export default (
13
+ <Router history={history}>
14
+ <Route path='/' component={Layout}>
15
+ <IndexRedirect to='/events' />
16
+ <Route path='/events' component={EventsSearch} />
17
+ <Route path='/events/:id' component={EventDetails} />
18
+ </Route>
19
+ </Router>
20
+ )
@@ -0,0 +1,15 @@
1
+ import { createStore, applyMiddleware, compose } from 'redux'
2
+ import thunkMiddleware from 'redux-thunk'
3
+
4
+ import reducer from 'reducers'
5
+
6
+ const store = createStore(
7
+ reducer,
8
+ {}, // initial state
9
+ compose(
10
+ applyMiddleware(thunkMiddleware), // for async actions
11
+ window.devToolsExtension ? window.devToolsExtension() : (f) => f
12
+ )
13
+ )
14
+
15
+ export default store
@@ -0,0 +1,50 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import { history } from 'routes'
4
+
5
+ import { fetchEventDetails } from 'actions/event-details'
6
+ import EventOverviewDialog from 'components/event-overview-dialog'
7
+ import EventRetryDialog from 'components/event-retry-dialog'
8
+
9
+ class EventDetails extends Component {
10
+ static get propTypes () {
11
+ return {
12
+ fetchEventDetails: PropTypes.func.isRequired
13
+ }
14
+ }
15
+
16
+ componentDidMount () {
17
+ this.props.params.id &&
18
+ this.props.fetchEventDetails({ id: this.props.params.id })
19
+ }
20
+
21
+ componentDidUpdate (prevProps) {
22
+ if (this.isDialogClosed(prevProps)) {
23
+ history.push('/events')
24
+ }
25
+ }
26
+
27
+ render () {
28
+ const details = this.props.eventDetails
29
+ const visible = !!details.id
30
+ const event = Object.assign({ overviewVisible: visible }, details)
31
+
32
+ return (
33
+ <div className='event-details'>
34
+ <EventOverviewDialog event={event} />
35
+ <EventRetryDialog event={event} />
36
+ </div>
37
+ )
38
+ }
39
+
40
+ isDialogClosed (prevProps) {
41
+ return prevProps.eventDetails.id !== undefined &&
42
+ this.props.eventDetails.id === undefined
43
+ }
44
+ }
45
+
46
+ export default connect(
47
+ (state) => state, {
48
+ fetchEventDetails
49
+ }
50
+ )(EventDetails)
@@ -0,0 +1,112 @@
1
+ import React, { Component, PropTypes } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import { EVENTS_SEARCH_LIMIT } from 'api'
4
+
5
+ import EventsList from 'components/events-list'
6
+ import SearchInput from 'components/search-input'
7
+ import CircularProgress from 'material-ui/CircularProgress'
8
+ import RaisedButton from 'material-ui/RaisedButton'
9
+
10
+ import { fetchSearchResults, loadMoreSearchResults } from 'actions/events-search'
11
+ import { changeSearchInputFilterType, changeSearchInputFilterValue } from 'actions/search-input-filter'
12
+ import { showEventOverview } from 'actions/event-overview'
13
+
14
+ export class EventsSearch extends Component {
15
+ static get propTypes () {
16
+ return {
17
+ fetchSearchResults: PropTypes.func.isRequired,
18
+ loadMoreSearchResults: PropTypes.func.isRequired,
19
+ changeSearchInputFilterType: PropTypes.func.isRequired,
20
+ changeSearchInputFilterValue: PropTypes.func.isRequired,
21
+ showEventOverview: PropTypes.func.isRequired
22
+ }
23
+ }
24
+
25
+ componentDidMount () {
26
+ const { type, value } = this.props.location.query
27
+ if (type && value) {
28
+ this.props.changeSearchInputFilterType(type)
29
+ this.props.changeSearchInputFilterValue(value)
30
+ }
31
+
32
+ this.props.events.length === 0 &&
33
+ this.props.fetchSearchResults({ offset: 0 })
34
+ }
35
+
36
+ render () {
37
+ const { events } = this.props
38
+ const { type, value } = this.props.eventsFilters
39
+
40
+ return (
41
+ <div className='events-search'>
42
+ <SearchInput filterType={type} filterValue={value}/>
43
+ <div>
44
+ <EventsList events={events} />
45
+ <LoadMore {...this.props} />
46
+ <EmptyEvent
47
+ events={events}
48
+ isFetchingEvents={this.props.xhrStatus.isFetchingEvents}/>
49
+ </div>
50
+ {
51
+ this.isFetchingFirstPage() &&
52
+ <div className='page-loader'>
53
+ <CircularProgress />
54
+ </div>
55
+ }
56
+ </div>
57
+ )
58
+ }
59
+
60
+ isFetchingFirstPage () {
61
+ return this.props.xhrStatus.isFetchingEvents &&
62
+ this.props.events.length === 0
63
+ }
64
+ }
65
+
66
+ class LoadMore extends Component {
67
+ static get propTypes () {
68
+ return {
69
+ loadMoreSearchResults: PropTypes.func.isRequired,
70
+ xhrStatus: PropTypes.shape({ lastEventsLoadSize: PropTypes.number }).isRequired
71
+ }
72
+ }
73
+
74
+ render () {
75
+ return (
76
+ this.props.xhrStatus.lastEventsLoadSize === EVENTS_SEARCH_LIMIT &&
77
+ <RaisedButton
78
+ label='Load more'
79
+ onClick={() => this.props.loadMoreSearchResults()}
80
+ style={{margin: '10px'}}/>
81
+ )
82
+ }
83
+ }
84
+
85
+ class EmptyEvent extends Component {
86
+ static get propTypes () {
87
+ return {
88
+ events: PropTypes.array.isRequired,
89
+ isFetchingEvents: PropTypes.bool.isRequired
90
+ }
91
+ }
92
+
93
+ render () {
94
+ return (
95
+ this.props.events.length === 0 &&
96
+ !this.props.isFetchingEvents &&
97
+ <div className='empty-event'>
98
+ No events found
99
+ </div>
100
+ )
101
+ }
102
+ }
103
+
104
+ export default connect(
105
+ (state) => state, {
106
+ fetchSearchResults,
107
+ loadMoreSearchResults,
108
+ changeSearchInputFilterType,
109
+ changeSearchInputFilterValue,
110
+ showEventOverview
111
+ }
112
+ )(EventsSearch)
@@ -0,0 +1,24 @@
1
+ .events-search {
2
+ max-width: 1020px;
3
+ min-height: 100%;
4
+
5
+ background-color: #e4e4e4;
6
+ margin: 0 auto;
7
+ padding: 20px 0;
8
+
9
+ .page-loader {
10
+ text-align: center;
11
+ padding: 50px 0;
12
+ }
13
+
14
+ .empty-event {
15
+ margin: 50px 15px 0;
16
+ font-size: 30px;
17
+ font-weight: lighter;
18
+ font-family: Roboto;
19
+ }
20
+
21
+ & > .event {
22
+ margin: 10px 10px;
23
+ }
24
+ }
@@ -0,0 +1,96 @@
1
+ import React from 'react'
2
+ import jasmineEnzyme from 'jasmine-enzyme'
3
+ import { mount } from 'enzyme'
4
+ import { EventsSearch } from 'views/events-search'
5
+ import getMuiTheme from 'material-ui/styles/getMuiTheme'
6
+ import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
7
+ import { Provider } from 'react-redux'
8
+ import configureMockStore from 'redux-mock-store'
9
+ import thunk from 'redux-thunk'
10
+
11
+ const middlewares = [ thunk ]
12
+ const mockStore = configureMockStore(middlewares)
13
+
14
+ const mountComponent = (store, props) => mount(
15
+ <Provider store={store}>
16
+ <MuiThemeProvider muiTheme={getMuiTheme()}>
17
+ <EventsSearch {...props} />
18
+ </MuiThemeProvider>
19
+ </Provider>
20
+ )
21
+
22
+ describe('view <EventsSearch />', () => {
23
+ let props, component, store
24
+
25
+ beforeEach(() => {
26
+ jasmineEnzyme()
27
+ store = mockStore({
28
+ xhrStatus: {
29
+ isRetryingEvent: false
30
+ }
31
+ })
32
+
33
+ props = {
34
+ fetchSearchResults: jasmine.createSpy('fetchSearchResults'),
35
+ loadMoreSearchResults: jasmine.createSpy('loadMoreSearchResults'),
36
+ changeSearchInputFilterType: jasmine.createSpy('changeSearchInputFilterType'),
37
+ changeSearchInputFilterValue: jasmine.createSpy('changeSearchInputFilterValue'),
38
+ showEventOverview: jasmine.createSpy('showEventOverview'),
39
+
40
+ xhrStatus: { isFetchingEvents: false, lastEventsLoadSize: 0 },
41
+ location: { query: {} },
42
+ eventsFilters: {},
43
+ events: [{ id: 1 }, { id: 2 }]
44
+ }
45
+
46
+ component = mountComponent(store, props)
47
+ })
48
+
49
+ it('renders <SearchInput />', () => {
50
+ expect(component.find('.search-input').length).toEqual(1)
51
+ })
52
+
53
+ it('renders <EventsList />', () => {
54
+ expect(component.find('.events-list').length).toEqual(1)
55
+ })
56
+
57
+ it('does not render <EventsSearch.EmptyEvent />', () => {
58
+ expect(component.find('.empty-event').length).toEqual(0)
59
+ })
60
+
61
+ describe('when it initializes with filter type and value', () => {
62
+ beforeEach(() => {
63
+ Object.assign(props, {
64
+ location: { query: {
65
+ type: 'entity_id',
66
+ value: '12345'
67
+ }}
68
+ })
69
+
70
+ component = mountComponent(store, props)
71
+ })
72
+
73
+ it('calls changeSearchInputFilterType with type', () => {
74
+ expect(props.changeSearchInputFilterType).toHaveBeenCalledWith('entity_id')
75
+ })
76
+
77
+ it('calls changeSearchInputFilterValue with value', () => {
78
+ expect(props.changeSearchInputFilterValue).toHaveBeenCalledWith('12345')
79
+ })
80
+ })
81
+
82
+ describe('when events is empty', () => {
83
+ beforeEach(() => {
84
+ Object.assign(props, { events: [] })
85
+ component = mountComponent(store, props)
86
+ })
87
+
88
+ it('calls fetchSearchResults with offset 0', () => {
89
+ expect(props.fetchSearchResults).toHaveBeenCalledWith({ offset: 0 })
90
+ })
91
+
92
+ it('renders <EventsSearch.EmptyEvent />', () => {
93
+ expect(component.find('.empty-event').length).toEqual(1)
94
+ })
95
+ })
96
+ })
@@ -0,0 +1,24 @@
1
+ import React, { Component } from 'react'
2
+
3
+ import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
4
+ import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
5
+ import getMuiTheme from 'material-ui/styles/getMuiTheme'
6
+
7
+ import Header from 'components/header'
8
+ import FlashMessageList from 'components/flash-message-list'
9
+
10
+ const theme = getMuiTheme(lightBaseTheme)
11
+
12
+ export default class extends Component {
13
+ render () {
14
+ return (
15
+ <MuiThemeProvider muiTheme={theme}>
16
+ <div className='layout'>
17
+ <Header />
18
+ <FlashMessageList />
19
+ {React.cloneElement(this.props.children)}
20
+ </div>
21
+ </MuiThemeProvider>
22
+ )
23
+ }
24
+ }
@@ -0,0 +1,3 @@
1
+ .layout {
2
+ height: 100%
3
+ }
@@ -0,0 +1,11 @@
1
+ module PhobosCheckpointUI
2
+ module App
3
+ def self.new(api_app, configs = {})
4
+ StaticApp.configs = configs
5
+ Rack::URLMap.new(
6
+ '/' => StaticApp,
7
+ '/api' => api_app
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module PhobosCheckpointUI
2
+ class StaticApp < Sinatra::Base
3
+ class << self
4
+ attr_accessor :configs
5
+ end
6
+
7
+ set :logging, nil
8
+ set :public_folder, -> { File.join(Dir.pwd, 'public') }
9
+
10
+ get '/configs' do
11
+ content_type :json
12
+ self.class.configs.to_json
13
+ end
14
+
15
+ get '/*' do
16
+ send_file File.join(settings.public_folder, 'index.html')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require 'rake'
2
+ require 'fileutils'
3
+
4
+ module PhobosCheckpointUI
5
+ module Tasks
6
+ extend Rake::DSL if defined? Rake::DSL
7
+
8
+ namespace :phobos_checkpoint_ui do
9
+ desc 'Copy Assets to ./public folder. It creates the folder if it doesn\'t exist'
10
+ task :copy_assets do
11
+ folder = File.join(Dir.pwd, 'public')
12
+ assets = File.expand_path(File.join(File.dirname(__FILE__), '../../assets'))
13
+ FileUtils.rm_rf(folder)
14
+ FileUtils.mkdir_p(folder)
15
+ FileUtils.cp_r(assets, File.join(folder, 'assets'))
16
+ FileUtils.mv(File.join(folder, 'assets/index.html'), File.join(folder, 'index.html'))
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module PhobosCheckpointUI
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,10 @@
1
+ require 'json'
2
+ require 'rack'
3
+ require 'sinatra'
4
+
5
+ require 'phobos_checkpoint_ui/version'
6
+ require 'phobos_checkpoint_ui/static_app'
7
+ require 'phobos_checkpoint_ui/app'
8
+
9
+ module PhobosCheckpointUI
10
+ end
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'phobos_checkpoint_ui/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'phobos_checkpoint_ui'
8
+ spec.version = PhobosCheckpointUI::VERSION
9
+ spec.authors = [
10
+ 'Túlio Ornelas',
11
+ 'Mathias Klippinge',
12
+ 'Sergey Evstifeev',
13
+ 'Thiago R. Colucci',
14
+ 'Martin Svalin',
15
+ 'Francisco Juan'
16
+ ]
17
+ spec.email = [
18
+ 'ornelas.tulio@gmail.com',
19
+ 'mathias.klippinge@gmail.com',
20
+ 'sergey.evstifeev@gmail.com',
21
+ 'ticolucci@gmail.com',
22
+ 'martin@lite.nu',
23
+ 'francisco.juan@gmail.com'
24
+ ]
25
+
26
+ spec.summary = 'Phobos Checkpoint UI is a GUI for phobos checkpoint API'
27
+ spec.description = 'Phobos Checkpoint UI is a GUI for phobos checkpoint API, it is compatible with https://github.com/klarna/phobos_db_checkpoint'
28
+ spec.homepage = 'https://github.com/klarna/phobos_checkpoint_ui'
29
+ spec.license = 'MIT'
30
+
31
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
32
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
33
+ if spec.respond_to?(:metadata)
34
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
35
+ else
36
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
37
+ end
38
+
39
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
40
+ spec.bindir = 'bin'
41
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
42
+ spec.require_paths = ['lib']
43
+
44
+ spec.add_development_dependency 'bundler', '~> 1.12'
45
+ spec.add_development_dependency 'rake', '~> 10.0'
46
+ spec.add_development_dependency 'rspec', '~> 3.0'
47
+ spec.add_development_dependency 'pry-byebug'
48
+ spec.add_development_dependency 'rack-test'
49
+ spec.add_development_dependency 'rspec_junit_formatter', '0.2.2'
50
+
51
+ spec.add_dependency 'rake'
52
+ spec.add_dependency 'sinatra'
53
+ end
data/screenshot1.png ADDED
Binary file
data/screenshot2.png ADDED
Binary file