OLD | NEW |
| (Empty) |
1 /* | |
2 This file is part of the Juju GUI, which lets users view and manage Juju | |
3 environments within a graphical interface (https://launchpad.net/juju-gui). | |
4 Copyright (C) 2012-2013 Canonical Ltd. | |
5 | |
6 This program is free software: you can redistribute it and/or modify it under | |
7 the terms of the GNU Affero General Public License version 3, as published by | |
8 the Free Software Foundation. | |
9 | |
10 This program is distributed in the hope that it will be useful, but WITHOUT | |
11 ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, | |
12 SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero | |
13 General Public License for more details. | |
14 | |
15 You should have received a copy of the GNU Affero General Public License along | |
16 with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 */ | |
18 | |
19 'use strict'; | |
20 | |
21 | |
22 /** | |
23 * Provide the service views and mixins. | |
24 * | |
25 * @module views | |
26 * @submodule views.services | |
27 */ | |
28 | |
29 YUI.add('juju-view-service', function(Y) { | |
30 | |
31 var views = Y.namespace('juju.views'), | |
32 Templates = views.Templates, | |
33 inspector = Y.namespace('juju.views.inspector'), | |
34 models = Y.namespace('juju.models'), | |
35 plugins = Y.namespace('juju.plugins'), | |
36 utils = Y.namespace('juju.views.utils'), | |
37 viewletNS = Y.namespace('juju.viewlets'); | |
38 | |
39 var removeServiceMixin = { | |
40 // Mixin attributes | |
41 events: { | |
42 '#destroy-service': { | |
43 click: 'confirmDestroy' | |
44 } | |
45 }, | |
46 | |
47 confirmDestroy: function(ev) { | |
48 ev.halt(); | |
49 // We wait to make the panel until now, because in the render method | |
50 // the container is not yet part of the document. | |
51 if (Y.Lang.isUndefined(this.panel)) { | |
52 this.panel = views.createModalPanel( | |
53 'Are you sure you want to destroy the service? ' + | |
54 'This cannot be undone.', | |
55 '#destroy-modal-panel', | |
56 'Destroy Service', | |
57 Y.bind(this.destroyService, this) | |
58 ); | |
59 } | |
60 this.panel.show(); | |
61 }, | |
62 | |
63 destroyService: function(ev) { | |
64 ev.preventDefault(); | |
65 var env = this.get('env'), | |
66 service = this.get('model'); | |
67 ev.target.set('disabled', true); | |
68 env.destroy_service( | |
69 service.get('id'), Y.bind(this._destroyCallback, this)); | |
70 }, | |
71 | |
72 _destroyCallback: function(ev) { | |
73 var db = this.get('db'), | |
74 getModelURL = this.get('getModelURL'), | |
75 service = this.get('model'), | |
76 service_id = service.get('id'); | |
77 | |
78 if (ev.err) { | |
79 db.notifications.add( | |
80 new models.Notification({ | |
81 title: 'Error destroying service', | |
82 message: 'Service name: ' + ev.service_name, | |
83 level: 'error', | |
84 link: getModelURL(service), | |
85 modelId: service | |
86 }) | |
87 ); | |
88 } else { | |
89 db.services.remove(service); | |
90 db.relations.remove( | |
91 db.relations.filter( | |
92 function(r) { | |
93 return Y.Array.some(r.get('endpoints'), function(ep) { | |
94 return ep[0] === service_id; | |
95 }); | |
96 } | |
97 )); | |
98 this.panel.hide(); | |
99 this.panel.destroy(); | |
100 this.fire('navigateTo', {url: this.get('nsRouter').url({gui: '/'})}); | |
101 db.fire('update'); | |
102 } | |
103 } | |
104 }; | |
105 | |
106 | |
107 /** | |
108 * @class ServiceViewBase | |
109 */ | |
110 var ServiceViewBase = Y.Base.create('ServiceViewBase', Y.View, | |
111 [views.JujuBaseView], { | |
112 | |
113 initializer: function() { | |
114 Y.mix(this, inspector.exposeButtonMixin, | |
115 undefined, undefined, undefined, true); | |
116 Y.mix(this, inspector.manageUnitsMixin, | |
117 undefined, undefined, undefined, true); | |
118 Y.mix(this, removeServiceMixin, undefined, undefined, undefined, | |
119 true); | |
120 | |
121 // Bind visualization resizing on window resize. | |
122 Y.on('windowresize', Y.bind(function() { | |
123 this.fitToWindow(); | |
124 }, this)); | |
125 }, | |
126 | |
127 getServiceTabs: function(href) { | |
128 var db = this.get('db'), | |
129 service = this.get('model'), | |
130 getModelURL = this.get('getModelURL'), | |
131 charmId = service.get('charm'), | |
132 charm = db.charms.getById(charmId), | |
133 charmUrl = (charm ? getModelURL(charm) : '#'); | |
134 | |
135 var tabs = [{ | |
136 href: getModelURL(service), | |
137 title: 'Units', | |
138 active: false | |
139 }, { | |
140 href: getModelURL(service, 'relations'), | |
141 title: 'Relations', | |
142 active: false | |
143 }, { | |
144 href: getModelURL(service, 'config'), | |
145 title: 'Settings', | |
146 active: false | |
147 }, { | |
148 href: charmUrl, | |
149 title: 'Charm', | |
150 active: false | |
151 }, { | |
152 href: getModelURL(service, 'constraints'), | |
153 title: 'Constraints', | |
154 active: false | |
155 }]; | |
156 | |
157 Y.each(tabs, function(value) { | |
158 if (value.href === href) { | |
159 value.active = true; | |
160 } | |
161 }); | |
162 | |
163 return tabs; | |
164 }, | |
165 | |
166 /** | |
167 Fit to window. Must be called after the container | |
168 has been added to the DOM. | |
169 | |
170 @method containerAttached | |
171 */ | |
172 containerAttached: function() { | |
173 this.fitToWindow(); | |
174 }, | |
175 | |
176 fitToWindow: function() { | |
177 function getHeight(node) { | |
178 if (!node) { | |
179 return 0; | |
180 } | |
181 return node.get('clientHeight'); | |
182 } | |
183 var container = this.get('container'), | |
184 viewContainer = container.one('.view-container'); | |
185 if (viewContainer) { | |
186 Y.fire('beforePageSizeRecalculation'); | |
187 var navbarHeight = getHeight(Y.one('.navbar')), | |
188 windowHeight = container.get('winHeight'), | |
189 headerHeight = getHeight(container.one( | |
190 '.service-header-partial')), | |
191 footerHeight = getHeight(container.one('.bottom-navbar')), | |
192 size = (Math.max(windowHeight, 600) - navbarHeight - | |
193 headerHeight - footerHeight - 19); | |
194 viewContainer.set('offsetHeight', size); | |
195 Y.fire('afterPageSizeRecalculation'); | |
196 } | |
197 }, | |
198 | |
199 /** | |
200 Reject callback for the model promise which creates an error | |
201 notification and then redirects the user to the evironment view | |
202 | |
203 @method noServiceAvailable | |
204 */ | |
205 noServiceAvailable: function() { | |
206 this.get('db').notifications.add( | |
207 new Y.juju.models.Notification({ | |
208 title: 'Service is not available', | |
209 message: 'The service you are trying to view does not exist', | |
210 level: 'error' | |
211 }) | |
212 ); | |
213 | |
214 this.fire('navigateTo', { | |
215 url: this.get('nsRouter').url({gui: '/'}) | |
216 }); | |
217 }, | |
218 | |
219 /** | |
220 Shared rendering method to render the loading service data view | |
221 | |
222 @method renderLoading | |
223 */ | |
224 renderLoading: function() { | |
225 var container = this.get('container'); | |
226 container.setHTML( | |
227 '<div class="alert">Loading service details...</div>'); | |
228 console.log('waiting on service data'); | |
229 }, | |
230 | |
231 /** | |
232 Shared rendering method to render the service data view | |
233 | |
234 @method renderData | |
235 */ | |
236 renderData: function() { | |
237 var container = this.get('container'); | |
238 var service = this.get('model'); | |
239 var db = this.get('db'); | |
240 var env = db.environment.get('annotations'); | |
241 container.setHTML(this.template(this.gatherRenderData())); | |
242 // to be able to use this same method for all service views | |
243 if (container.one('.landscape-controls')) { | |
244 Y.juju.views.utils.updateLandscapeBottomBar(this.get('landscape'), | |
245 env, service, container); | |
246 } | |
247 }, | |
248 | |
249 /** | |
250 Shared render method to be used in service detail views | |
251 | |
252 @method render | |
253 @return {Object} view instance. | |
254 */ | |
255 render: function() { | |
256 var model = this.get('model'); | |
257 if (!model) { | |
258 this.renderLoading(); | |
259 } else { | |
260 this.renderData(); | |
261 } | |
262 return this; | |
263 } | |
264 | |
265 }); | |
266 views.serviceBase = ServiceViewBase; | |
267 | |
268 /** | |
269 * @class ServiceRelationsView | |
270 */ | |
271 views.service_relations = Y.Base.create( | |
272 'ServiceRelationsView', ServiceViewBase, [ | |
273 views.JujuBaseView], { | |
274 | |
275 template: Templates['service-relations'], | |
276 | |
277 events: { | |
278 '#service-relations .btn': {click: 'confirmRemoved'} | |
279 }, | |
280 | |
281 /** | |
282 * Gather up all of the data required for the template. | |
283 * | |
284 * Aside from a nice separation of concerns, this method also | |
285 * facilitates testing. | |
286 * | |
287 * @method gatherRenderData | |
288 * @return {Object} The data the template will render. | |
289 */ | |
290 gatherRenderData: function() { | |
291 var service = this.get('model'), | |
292 db = this.get('db'), | |
293 querystring = this.get('querystring'); | |
294 var relation_data = utils.getRelationDataForService(db, service); | |
295 Y.each(relation_data, function(rel) { | |
296 if (rel.elementId === querystring.rel_id) { | |
297 rel.highlight = true; | |
298 } | |
299 }); | |
300 var charm_id = service.get('charm'), | |
301 charm = db.charms.getById(charm_id), | |
302 charm_attrs = charm ? charm.getAttrs() : undefined; | |
303 return { | |
304 viewName: 'relations', | |
305 tabs: this.getServiceTabs('relations'), | |
306 service: service.getAttrs(), | |
307 landscape: this.get('landscape'), | |
308 serviceModel: service, | |
309 relations: relation_data, | |
310 charm: charm_attrs, | |
311 charm_id: charm_id, | |
312 serviceIsJujuGUI: utils.isGuiCharmUrl(charm_id), | |
313 serviceRemoteUri: this.get('nsRouter').url({ gui: '/service/'}) | |
314 }; | |
315 }, | |
316 | |
317 confirmRemoved: function(ev) { | |
318 // We wait to make the panel until now, because in the render method | |
319 // the container is not yet part of the document. | |
320 ev.preventDefault(); | |
321 if (Y.Lang.isUndefined(this.remove_panel)) { | |
322 this.remove_panel = views.createModalPanel( | |
323 'Are you sure you want to remove this service relation? ' + | |
324 'This action cannot be undone, though you can ' + | |
325 'recreate it later.', | |
326 '#remove-modal-panel'); | |
327 } | |
328 // We set the buttons separately every time because we want to bind | |
329 // the target, which can vary. Since the page is redrawn after a | |
330 // relation is removed, this is technically unnecessary in this | |
331 // particular case, but a good pattern to get into. | |
332 views.setModalButtons( | |
333 this.remove_panel, | |
334 'Remove Service Relation', | |
335 Y.bind(this.doRemoveRelation, this, ev.target)); | |
336 this.remove_panel.show(); | |
337 }, | |
338 | |
339 doRemoveRelation: function(button, ev) { | |
340 ev.preventDefault(); | |
341 var rel_id = button.get('value'), | |
342 db = this.get('db'), | |
343 env = this.get('env'), | |
344 service = this.get('model'), | |
345 relation = db.relations.getById(rel_id), | |
346 endpoints = relation.get('endpoints'), | |
347 endpoint_a = endpoints[0], | |
348 endpoint_b; | |
349 | |
350 if (endpoints.length === 1) { | |
351 // For a peer relationship, both endpoints are the same. | |
352 endpoint_b = endpoint_a; | |
353 } else { | |
354 endpoint_b = endpoints[1]; | |
355 } | |
356 | |
357 ev.target.set('disabled', true); | |
358 | |
359 env.remove_relation( | |
360 endpoint_a, | |
361 endpoint_b, | |
362 Y.bind(this._removeRelationCallback, this, | |
363 relation, button, ev.target)); | |
364 }, | |
365 | |
366 _removeRelationCallback: function(relation, rm_button, | |
367 confirm_button, ev) { | |
368 var db = this.get('db'), | |
369 getModelURL = this.get('getModelURL'), | |
370 service = this.get('model'); | |
371 views.highlightRow(rm_button.ancestor('tr'), ev.err); | |
372 if (ev.err) { | |
373 db.notifications.add( | |
374 new models.Notification({ | |
375 title: 'Error deleting relation', | |
376 message: 'Relation ' + ev.endpoint_a + ' to ' + ev.endpoint_b, | |
377 level: 'error', | |
378 link: getModelURL(service) + 'relations?rel_id=' + | |
379 rm_button.get('id'), | |
380 modelId: relation | |
381 }) | |
382 ); | |
383 } else { | |
384 db.relations.remove(relation); | |
385 db.fire('update'); | |
386 } | |
387 confirm_button.set('disabled', false); | |
388 this.remove_panel.hide(); | |
389 } | |
390 }); | |
391 | |
392 /** | |
393 * @class ServiceConstraintsView | |
394 */ | |
395 views.service_constraints = Y.Base.create( | |
396 'ServiceConstraintsView', ServiceViewBase, [ | |
397 views.JujuBaseView], { | |
398 | |
399 template: Templates['service-constraints'], | |
400 | |
401 events: { | |
402 '#save-service-constraints': {click: 'updateConstraints'} | |
403 }, | |
404 | |
405 updateConstraints: function() { | |
406 var service = this.get('model'), | |
407 container = this.get('container'), | |
408 env = this.get('env'); | |
409 var constraints = utils.getElementsValuesMapping( | |
410 container, '.constraint-field'); | |
411 | |
412 // Disable the "Update" button while the RPC call is outstanding. | |
413 container.one('#save-service-constraints') | |
414 .set('disabled', 'disabled'); | |
415 env.set_constraints(service.get('id'), | |
416 constraints, | |
417 Y.bind(this._setConstraintsCallback, this, container) | |
418 ); | |
419 }, | |
420 | |
421 _setConstraintsCallback: function(container, ev) { | |
422 var service = this.get('model'), | |
423 env = this.get('env'), | |
424 getModelURL = this.get('getModelURL'), | |
425 db = this.get('db'); | |
426 | |
427 if (ev.err) { | |
428 db.notifications.add( | |
429 new models.Notification({ | |
430 title: 'Error setting service constraints', | |
431 message: 'Service name: ' + ev.service_name, | |
432 level: 'error', | |
433 link: getModelURL(service) + 'constraints', | |
434 modelId: service | |
435 }) | |
436 ); | |
437 container.one('#save-service-constraints') | |
438 .removeAttribute('disabled'); | |
439 | |
440 } else { | |
441 // The usual result of a successful request is a page refresh. | |
442 // Therefore, we need to set this delay in order to show the | |
443 // "success" message after the page page refresh. | |
444 setTimeout(function() { | |
445 utils.showSuccessMessage(container, 'Constraints updated'); | |
446 }, 1000); | |
447 } | |
448 }, | |
449 | |
450 /** | |
451 * Gather up all of the data required for the template. | |
452 * | |
453 * Aside from a nice separation of concerns, this method also | |
454 * facilitates testing. | |
455 * | |
456 * @method gatherRenderData | |
457 * @return {Object} The data the template will render. | |
458 */ | |
459 gatherRenderData: function() { | |
460 var service = this.get('model'), | |
461 env = this.get('env'), | |
462 constraints = service.get('constraints'), | |
463 display_constraints = []; | |
464 | |
465 //these are read-only values | |
466 var readOnlyConstraints = { | |
467 'provider-type': constraints['provider-type'], | |
468 'ubuntu-series': constraints['ubuntu-series'] | |
469 }; | |
470 | |
471 Y.Object.each(constraints, function(value, name) { | |
472 if (!(name in readOnlyConstraints)) { | |
473 display_constraints.push({ | |
474 name: name, | |
475 value: value}); | |
476 } | |
477 }); | |
478 | |
479 Y.Array.each(env.genericConstraints, function(gkey) { | |
480 if (!(gkey in constraints)) { | |
481 display_constraints.push({name: gkey, value: ''}); | |
482 } | |
483 }); | |
484 | |
485 console.log('service constraints', display_constraints); | |
486 var charm_id = service.get('charm'); | |
487 return { | |
488 viewName: 'constraints', | |
489 tabs: this.getServiceTabs('constraints'), | |
490 service: service.getAttrs(), | |
491 landscape: this.get('landscape'), | |
492 serviceModel: service, | |
493 constraints: display_constraints, | |
494 readOnlyConstraints: (function() { | |
495 var arr = []; | |
496 Y.Object.each(readOnlyConstraints, function(name, value) { | |
497 arr.push({name: name, value: value}); | |
498 }); | |
499 return arr; | |
500 })(), | |
501 charm_id: charm_id, | |
502 serviceIsJujuGUI: utils.isGuiCharmUrl(charm_id) | |
503 }; | |
504 } | |
505 | |
506 }); | |
507 | |
508 /** | |
509 * @class ServiceConfigView | |
510 */ | |
511 views.service_config = Y.Base.create( | |
512 'ServiceConfigView', ServiceViewBase, [ | |
513 views.JujuBaseView], { | |
514 | |
515 template: Templates['service-config'], | |
516 | |
517 events: { | |
518 '#save-service-config': {click: 'saveConfig'} | |
519 }, | |
520 | |
521 /** | |
522 * Gather up all of the data required for the template. | |
523 * | |
524 * Aside from a nice separation of concerns, this method also | |
525 * facilitates testing. | |
526 * | |
527 * @method gatherRenderData | |
528 * @return {Object} The data the template will render. | |
529 */ | |
530 gatherRenderData: function() { | |
531 var db = this.get('db'); | |
532 var service = this.get('model'); | |
533 var charm = db.charms.getById(service.get('charm')); | |
534 var config = service.get('config'); | |
535 var schema = charm.get('options'); | |
536 var charm_id = service.get('charm'); | |
537 | |
538 var settings = utils.extractServiceSettings(schema, config); | |
539 | |
540 return { | |
541 viewName: 'config', | |
542 tabs: this.getServiceTabs('config'), | |
543 service: service.getAttrs(), | |
544 settings: settings, | |
545 charm_id: charm_id, | |
546 landscape: this.get('landscape'), | |
547 serviceModel: service, | |
548 serviceIsJujuGUI: utils.isGuiCharmUrl(charm_id) | |
549 }; | |
550 }, | |
551 | |
552 /** | |
553 Attach the plugins. Must be called after the container | |
554 has been added to the DOM. | |
555 | |
556 @method containerAttached | |
557 */ | |
558 containerAttached: function() { | |
559 this.constructor.superclass.containerAttached.call(this); | |
560 var container = this.get('container'); | |
561 container.all('textarea.config-field').plug(plugins.ResizingTextarea, | |
562 { max_height: 200, | |
563 min_height: 18, | |
564 single_line: 18}); | |
565 }, | |
566 | |
567 showErrors: function(errors) { | |
568 var container = this.get('container'); | |
569 container.one('#save-service-config').removeAttribute('disabled'); | |
570 | |
571 | |
572 // Remove old error messages | |
573 container.all('.help-inline').each(function(node) { | |
574 node.remove(); | |
575 }); | |
576 | |
577 // Remove remove the "error" class from the "div" | |
578 // that previously had "help-inline" tags | |
579 container.all('.error').each(function(node) { | |
580 node.removeClass('error'); | |
581 }); | |
582 | |
583 var firstErrorKey = null; | |
584 Y.Object.each(errors, function(value, key) { | |
585 var errorTag = Y.Node.create('<span/>') | |
586 .set('id', 'error-' + key) | |
587 .addClass('help-inline'); | |
588 | |
589 var field = container.one('#input-' + key); | |
590 // Add the "error" class to the wrapping "control-group" div | |
591 field.get('parentNode').get('parentNode').addClass('error'); | |
592 | |
593 errorTag.appendTo(field.get('parentNode')); | |
594 | |
595 errorTag.setHTML(value); | |
596 if (!firstErrorKey) { | |
597 firstErrorKey = key; | |
598 } | |
599 }); | |
600 | |
601 if (firstErrorKey) { | |
602 var field = container.one('#input-' + firstErrorKey); | |
603 field.focus(); | |
604 } | |
605 }, | |
606 | |
607 saveConfig: function() { | |
608 var env = this.get('env'), | |
609 db = this.get('db'), | |
610 getModelURL = this.get('getModelURL'), | |
611 service = this.get('model'), | |
612 charm_url = service.get('charm'), | |
613 charm = db.charms.getById(charm_url), | |
614 schema = charm.get('options'), | |
615 container = this.get('container'); | |
616 | |
617 // Disable the "Update" button while the RPC call is outstanding. | |
618 container.one('#save-service-config').set('disabled', 'disabled'); | |
619 | |
620 var new_values = utils.getElementsValuesMapping( | |
621 container, '.config-field'); | |
622 var errors = utils.validate(new_values, schema); | |
623 | |
624 if (Y.Object.isEmpty(errors)) { | |
625 env.set_config( | |
626 service.get('id'), | |
627 new_values, | |
628 null, | |
629 service.get('config'), | |
630 Y.bind(this._setConfigCallback, this, container) | |
631 ); | |
632 | |
633 } else { | |
634 this.showErrors(errors); | |
635 } | |
636 }, | |
637 | |
638 _setConfigCallback: function(container, ev) { | |
639 var service = this.get('model'), | |
640 env = this.get('env'), | |
641 getModelURL = this.get('getModelURL'), | |
642 db = this.get('db'); | |
643 | |
644 if (ev.err) { | |
645 db.notifications.add( | |
646 new models.Notification({ | |
647 title: 'Error setting service config', | |
648 message: 'Service name: ' + ev.service_name, | |
649 level: 'error', | |
650 link: getModelURL(service) + 'config', | |
651 modelId: service | |
652 }) | |
653 ); | |
654 container.one('#save-service-config') | |
655 .removeAttribute('disabled'); | |
656 | |
657 } else { | |
658 // The usual result of a successful request is a page refresh. | |
659 // Therefore, we need to set this delay in order to show the | |
660 // "success" message after the page page refresh. | |
661 setTimeout(function() { | |
662 utils.showSuccessMessage(container, 'Settings updated'); | |
663 }, 1000); | |
664 } | |
665 } | |
666 }); | |
667 | |
668 // Display a unit grid based on the total number of units. | |
669 Y.Handlebars.registerHelper('show_units', function(units) { | |
670 var template; | |
671 var numUnits = units.length; | |
672 // TODO: different visualization based on the viewport size. | |
673 if (numUnits <= 25) { | |
674 template = Templates.show_units_large; | |
675 } else if (numUnits <= 50) { | |
676 template = Templates.show_units_medium; | |
677 } else if (numUnits <= 250) { | |
678 template = Templates.show_units_small; | |
679 } else { | |
680 template = Templates.show_units_tiny; | |
681 } | |
682 return template({units: units}); | |
683 }); | |
684 | |
685 // Translate the given state to the matching style. | |
686 Y.Handlebars.registerHelper('state_to_style', function(state) { | |
687 // Using a closure to avoid the second argument to be passed through. | |
688 return utils.stateToStyle(state); | |
689 }); | |
690 | |
691 /** | |
692 * @class ServiceView | |
693 */ | |
694 var ServiceView = Y.Base.create('ServiceView', ServiceViewBase, [ | |
695 views.JujuBaseView], { | |
696 | |
697 template: Templates.service, | |
698 | |
699 /** | |
700 * Gather up all of the data required for the template. | |
701 * | |
702 * Aside from a nice separation of concerns, this method also | |
703 * facilitates testing. | |
704 * | |
705 * @method gatherRenderData | |
706 * @return {Object} The data the template will render. | |
707 */ | |
708 gatherRenderData: function() { | |
709 var db = this.get('db'); | |
710 var service = this.get('model'); | |
711 var filter_state = this.get('querystring').state; | |
712 var units = db.units.get_units_for_service(service); | |
713 var charm_id = service.get('charm'); | |
714 var charm = db.charms.getById(charm_id); | |
715 var charm_attrs = charm ? charm.getAttrs() : undefined; | |
716 var state_data = [{ | |
717 title: 'All', | |
718 link: '.', | |
719 active: !filter_state, | |
720 count: this.filterUnits(null, units).length | |
721 }]; | |
722 Y.each(['Running', 'Pending', 'Error'], function(title) { | |
723 var lower = title.toLowerCase(); | |
724 state_data.push({ | |
725 title: title, | |
726 active: lower === filter_state, | |
727 count: this.filterUnits(lower, units).length, | |
728 link: '?state=' + lower}); | |
729 }, this); | |
730 return { | |
731 viewName: 'units', | |
732 landscape: this.get('landscape'), | |
733 serviceModel: service, | |
734 tabs: this.getServiceTabs('.'), | |
735 service: service.getAttrs(), | |
736 charm_id: charm_id, | |
737 charm: charm_attrs, | |
738 serviceIsJujuGUI: utils.isGuiCharmUrl(charm_id), | |
739 state: filter_state, | |
740 units: this.filterUnits(filter_state, units), | |
741 states: state_data | |
742 }; | |
743 }, | |
744 | |
745 filterUnits: function(filter_state, units) { | |
746 // If filtering was requested, do it. | |
747 if (filter_state) { | |
748 // Build a matcher that will identify units of the requested state. | |
749 var matcher = function(unit) { | |
750 // Is this unit's (simplified) state the one we are looking for? | |
751 return utils.simplifyState(unit) === filter_state; | |
752 }; | |
753 return Y.Array.filter(units, matcher); | |
754 } else { // Otherwise just return all the units we were given. | |
755 return units; | |
756 } | |
757 }, | |
758 | |
759 events: { | |
760 'div.unit': {click: function(ev) { | |
761 var id = ev.currentTarget.get('id'); | |
762 console.log('Unit clicked', id); | |
763 this.fire('navigateTo', { | |
764 url: this.get('nsRouter').url({ | |
765 gui: '/unit/' + id.replace('/', '-') + '/' | |
766 }) | |
767 }); | |
768 }} | |
769 } | |
770 }, { | |
771 ATTRS: { | |
772 /** | |
773 Applications router utility methods | |
774 | |
775 @attribute nsRouter | |
776 */ | |
777 nsRouter: {} | |
778 } | |
779 }); | |
780 | |
781 views.service = ServiceView; | |
782 | |
783 }, '0.1.0', { | |
784 requires: [ | |
785 'base-build', | |
786 'd3-statusbar', | |
787 'event-key', | |
788 'event-resize', | |
789 'handlebars', | |
790 'juju-databinding', | |
791 'juju-models', | |
792 'juju-view-container', | |
793 'juju-view-inspector', | |
794 'juju-view-utils', | |
795 'juju-templates', | |
796 'node', | |
797 'panel', | |
798 'transition', | |
799 'view', | |
800 'event-resize' | |
801 ] | |
802 }); | |
OLD | NEW |