1
+ /* eslint no-console:0 */
2
+
1
3
var pkg = require ( '../package.json' ) ;
2
4
var app = require ( 'ampersand-app' ) ;
3
5
app . extend ( {
@@ -8,7 +10,9 @@ app.extend({
8
10
'App Version' : pkg . version
9
11
}
10
12
} ) ;
11
- require ( './bugsnag' ) . listen ( app ) ;
13
+
14
+ var bugsnag = require ( './bugsnag' ) ;
15
+ bugsnag . listen ( app ) ;
12
16
13
17
var _ = require ( 'lodash' ) ;
14
18
var domReady = require ( 'domready' ) ;
@@ -17,6 +21,13 @@ var getOrCreateClient = require('scout-client');
17
21
var ViewSwitcher = require ( 'ampersand-view-switcher' ) ;
18
22
var View = require ( 'ampersand-view' ) ;
19
23
var localLinks = require ( 'local-links' ) ;
24
+
25
+ var QueryOptions = require ( './models/query-options' ) ;
26
+ var Connection = require ( './models/connection' ) ;
27
+ var MongoDBInstance = require ( './models/mongodb-instance' ) ;
28
+ var Router = require ( './router' ) ;
29
+ var Statusbar = require ( './statusbar' ) ;
30
+
20
31
var debug = require ( 'debug' ) ( 'scout:app' ) ;
21
32
22
33
// Inter-process communication with main process (Electron window)
@@ -78,24 +89,62 @@ var Application = View.extend({
78
89
/**
79
90
* Enable/Disable features with one global switch
80
91
*/
81
- features : 'object'
92
+ features : 'object' ,
93
+ clientStartedAt : 'date' ,
94
+ clientStalledTimeout : 'number'
82
95
} ,
83
96
events : {
84
97
'click a' : 'onLinkClick'
85
98
} ,
86
- /**
87
- * We have what we need, we can now start our router and show the appropriate page!
88
- */
89
- _onDOMReady : function ( ) {
90
- this . el = document . querySelector ( '#application' ) ;
91
- this . render ( ) ;
99
+ onClientReady : function ( ) {
100
+ debug ( 'Client ready! Took %dms to become readable' ,
101
+ new Date ( ) - this . clientStartedAt ) ;
102
+
103
+ debug ( 'clearing client stall timeout...' ) ;
104
+ clearTimeout ( this . clientStalledTimeout ) ;
105
+
106
+ debug ( 'initializing singleton models... ' ) ;
107
+ this . queryOptions = new QueryOptions ( ) ;
108
+ this . volatileQueryOptions = new QueryOptions ( ) ;
109
+ this . instance = new MongoDBInstance ( ) ;
92
110
111
+ this . startRouter ( ) ;
112
+ } ,
113
+ startRouter : function ( ) {
114
+ this . router = new Router ( ) ;
115
+ debug ( 'Listening for page changes from the router...' ) ;
93
116
this . listenTo ( this . router , 'page' , this . onPageChange ) ;
94
117
118
+ debug ( 'Starting router...' ) ;
95
119
this . router . history . start ( {
96
120
pushState : false ,
97
121
root : '/'
98
122
} ) ;
123
+ app . statusbar . hide ( ) ;
124
+ } ,
125
+ onFatalError : function ( id , err ) {
126
+ debug ( 'clearing client stall timeout...' ) ;
127
+ clearTimeout ( this . clientStalledTimeout ) ;
128
+
129
+ console . error ( 'Fatal Error!: ' , id , err ) ;
130
+ bugsnag . notifyException ( err , 'Fatal Error: ' + id ) ;
131
+ app . statusbar . fatal ( err ) ;
132
+ } ,
133
+ // ms we'll wait for a `scout-client` instance
134
+ // to become readable before giving up and showing
135
+ // a fatal error message.
136
+ CLIENT_STALLED_REDLINE : 5 * 1000 ,
137
+ startClientStalledTimer : function ( ) {
138
+ this . clientStartedAt = new Date ( ) ;
139
+
140
+ debug ( 'Starting client stalled timer to bail in %dms...' ,
141
+ this . CLIENT_STALLED_REDLINE ) ;
142
+
143
+ this . clientStalledTimeout = setTimeout ( function ( ) {
144
+ this . onFatalError ( 'client stalled' ,
145
+ new Error ( 'Error connecting to MongoDB. '
146
+ + 'Please reload the page.' ) ) ;
147
+ } . bind ( this ) , this . CLIENT_STALLED_REDLINE ) ;
99
148
} ,
100
149
/**
101
150
* When you want to go to a different page in the app or just save
@@ -117,15 +166,25 @@ var Application = View.extend({
117
166
trigger : ! options . silent
118
167
} ) ;
119
168
} ,
169
+ /**
170
+ * Called a soon as the DOM is ready so we can
171
+ * start showing status indicators as
172
+ * quickly as possible.
173
+ */
120
174
render : function ( ) {
175
+ debug ( 'Rendering app container...' ) ;
176
+
177
+ this . el = document . querySelector ( '#application' ) ;
121
178
this . renderWithTemplate ( this ) ;
122
179
this . pageSwitcher = new ViewSwitcher ( this . queryByHook ( 'layout-container' ) , {
123
180
show : function ( ) {
124
181
document . scrollTop = 0 ;
125
182
}
126
183
} ) ;
127
-
128
- this . statusbar . el = this . queryByHook ( 'statusbar' ) ;
184
+ debug ( 'rendering statusbar...' ) ;
185
+ this . statusbar = new Statusbar ( {
186
+ el : this . queryByHook ( 'statusbar' )
187
+ } ) ;
129
188
this . statusbar . render ( ) ;
130
189
} ,
131
190
onPageChange : function ( view ) {
@@ -153,47 +212,68 @@ var state = new Application({
153
212
connection_id : connection_id
154
213
} ) ;
155
214
156
- var QueryOptions = require ( './models/query-options' ) ;
157
- var Connection = require ( './models/connection' ) ;
158
- var MongoDBInstance = require ( './models/mongodb-instance' ) ;
159
- var Router = require ( './router' ) ;
160
- var Statusbar = require ( './statusbar' ) ;
161
-
162
- function start ( ) {
163
- state . router = new Router ( ) ;
164
- domReady ( state . _onDOMReady . bind ( state ) ) ;
165
- }
215
+ // @todo (imlucas): Feature flags can be overrideen
216
+ // via `window.localStorage`.
217
+ var FEATURES = {
218
+ querybuilder : true ,
219
+ 'Connect with SSL' : false ,
220
+ 'Connect with Kerberos' : true ,
221
+ 'Connect with LDAP' : false ,
222
+ 'Connect with X.509' : false
223
+ } ;
166
224
167
225
app . extend ( {
168
226
client : null ,
227
+ // @note (imlucas): Backwards compat for querybuilder
228
+ features : FEATURES ,
229
+ /**
230
+ * Check whether a feature flag is currently enabled.
231
+ *
232
+ * @param {String } id - A key in `FEATURES`.
233
+ * @return {Boolean }
234
+ */
235
+ isFeatureEnabled : function ( id ) {
236
+ return FEATURES [ id ] === true ;
237
+ } ,
169
238
init : function ( ) {
170
- // feature flags
171
- this . features = {
172
- querybuilder : true
173
- } ;
174
- state . statusbar = new Statusbar ( ) ;
239
+ domReady ( function ( ) {
240
+ state . render ( ) ;
241
+
242
+ if ( ! connection_id ) {
243
+ // Not serving a part of the app which uses the client,
244
+ // so we can just start everything up now.
245
+ state . startRouter ( ) ;
246
+ return ;
247
+ }
248
+
249
+ app . statusbar . show ( 'Retrieving connection details...' ) ;
175
250
176
- if ( connection_id ) {
177
251
state . connection = new Connection ( {
178
252
_id : connection_id
179
253
} ) ;
180
254
181
-
182
255
debug ( 'looking up connection `%s`...' , connection_id ) ;
183
256
state . connection . fetch ( {
184
257
success : function ( ) {
185
- debug ( 'got connection `%j`...' , state . connection . serialize ( ) ) ;
186
- app . client = getOrCreateClient ( app . endpoint , state . connection . serialize ( ) ) ;
258
+ app . statusbar . show ( 'Connection details loaded! Initializing client...' ) ;
259
+
260
+ var endpoint = app . endpoint ;
261
+ var connection = state . connection . serialize ( ) ;
262
+
263
+ app . client = getOrCreateClient ( endpoint , connection )
264
+ . on ( 'readable' , state . onClientReady . bind ( state ) )
265
+ . on ( 'error' , state . onFatalError . bind ( state , 'create client' ) ) ;
187
266
188
- state . queryOptions = new QueryOptions ( ) ;
189
- state . volatileQueryOptions = new QueryOptions ( ) ;
190
- state . instance = new MongoDBInstance ( ) ;
191
- start ( ) ;
267
+ state . startClientStalledTimer ( ) ;
268
+ } ,
269
+ error : function ( ) {
270
+ // @todo (imlucas) `ampersand-sync-localforage` currently drops
271
+ // the real error so for now just use a generic.
272
+ state . onFatalError ( state , 'fetch connection' ,
273
+ new Error ( 'Error retrieving connection. Please reload the page.' ) ) ;
192
274
}
193
275
} ) ;
194
- } else {
195
- start ( ) ;
196
- }
276
+ } ) ;
197
277
// set up ipc
198
278
ipc . on ( 'message' , state . onMessageReceived . bind ( this ) ) ;
199
279
} ,
0 commit comments