1+ /* eslint no-console:0 */
2+
13var pkg = require ( '../package.json' ) ;
24var app = require ( 'ampersand-app' ) ;
35app . extend ( {
@@ -8,7 +10,9 @@ app.extend({
810 'App Version' : pkg . version
911 }
1012} ) ;
11- require ( './bugsnag' ) . listen ( app ) ;
13+
14+ var bugsnag = require ( './bugsnag' ) ;
15+ bugsnag . listen ( app ) ;
1216
1317var _ = require ( 'lodash' ) ;
1418var domReady = require ( 'domready' ) ;
@@ -17,6 +21,13 @@ var getOrCreateClient = require('scout-client');
1721var ViewSwitcher = require ( 'ampersand-view-switcher' ) ;
1822var View = require ( 'ampersand-view' ) ;
1923var 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+
2031var debug = require ( 'debug' ) ( 'scout:app' ) ;
2132
2233// Inter-process communication with main process (Electron window)
@@ -78,24 +89,62 @@ var Application = View.extend({
7889 /**
7990 * Enable/Disable features with one global switch
8091 */
81- features : 'object'
92+ features : 'object' ,
93+ clientStartedAt : 'date' ,
94+ clientStalledTimeout : 'number'
8295 } ,
8396 events : {
8497 'click a' : 'onLinkClick'
8598 } ,
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 ( ) ;
92110
111+ this . startRouter ( ) ;
112+ } ,
113+ startRouter : function ( ) {
114+ this . router = new Router ( ) ;
115+ debug ( 'Listening for page changes from the router...' ) ;
93116 this . listenTo ( this . router , 'page' , this . onPageChange ) ;
94117
118+ debug ( 'Starting router...' ) ;
95119 this . router . history . start ( {
96120 pushState : false ,
97121 root : '/'
98122 } ) ;
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 ) ;
99148 } ,
100149 /**
101150 * When you want to go to a different page in the app or just save
@@ -117,15 +166,25 @@ var Application = View.extend({
117166 trigger : ! options . silent
118167 } ) ;
119168 } ,
169+ /**
170+ * Called a soon as the DOM is ready so we can
171+ * start showing status indicators as
172+ * quickly as possible.
173+ */
120174 render : function ( ) {
175+ debug ( 'Rendering app container...' ) ;
176+
177+ this . el = document . querySelector ( '#application' ) ;
121178 this . renderWithTemplate ( this ) ;
122179 this . pageSwitcher = new ViewSwitcher ( this . queryByHook ( 'layout-container' ) , {
123180 show : function ( ) {
124181 document . scrollTop = 0 ;
125182 }
126183 } ) ;
127-
128- this . statusbar . el = this . queryByHook ( 'statusbar' ) ;
184+ debug ( 'rendering statusbar...' ) ;
185+ this . statusbar = new Statusbar ( {
186+ el : this . queryByHook ( 'statusbar' )
187+ } ) ;
129188 this . statusbar . render ( ) ;
130189 } ,
131190 onPageChange : function ( view ) {
@@ -153,47 +212,68 @@ var state = new Application({
153212 connection_id : connection_id
154213} ) ;
155214
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+ } ;
166224
167225app . extend ( {
168226 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+ } ,
169238 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...' ) ;
175250
176- if ( connection_id ) {
177251 state . connection = new Connection ( {
178252 _id : connection_id
179253 } ) ;
180254
181-
182255 debug ( 'looking up connection `%s`...' , connection_id ) ;
183256 state . connection . fetch ( {
184257 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' ) ) ;
187266
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.' ) ) ;
192274 }
193275 } ) ;
194- } else {
195- start ( ) ;
196- }
276+ } ) ;
197277 // set up ipc
198278 ipc . on ( 'message' , state . onMessageReceived . bind ( this ) ) ;
199279 } ,
0 commit comments