phobos_checkpoint_ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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