diff --git a/app/authenticated/route.js b/app/authenticated/route.js index 85f2f7319..732dc43ee 100644 --- a/app/authenticated/route.js +++ b/app/authenticated/route.js @@ -323,7 +323,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, { if ( volume ) { var mounts = volume.get('mounts'); - if ( !Ember.isArray('mounts') ) + if ( !Ember.isArray(mounts) ) { mounts = []; volume.set('mounts',mounts); diff --git a/app/components/page-header/template.hbs b/app/components/page-header/template.hbs index 79518dbc0..94e13ba01 100644 --- a/app/components/page-header/template.hbs +++ b/app/components/page-header/template.hbs @@ -136,7 +136,6 @@ {{#if isInfrastructure}} {{#link-to "hosts"}} Hosts{{/link-to}} {{#link-to "containers"}} Containers{{/link-to}} - {{#link-to "volumes"}} Volumes{{/link-to}} {{#link-to "loadbalancers"}} Load Balancers{{/link-to}} {{#link-to "loadbalancerconfigs"}} Balancer Configs{{/link-to}} {{/if}} diff --git a/app/components/resource-actions-menu/component.js b/app/components/resource-actions-menu/component.js index b9ffd0cb3..380e24ade 100644 --- a/app/components/resource-actions-menu/component.js +++ b/app/components/resource-actions-menu/component.js @@ -5,6 +5,7 @@ export default Ember.Component.extend({ choices: null, classNames: ['resource-actions'], + classNameBindings: ['activeActions.length::hide'], open: false, didInsertElement: function() { diff --git a/app/host/storage/controller.js b/app/host/storage/controller.js new file mode 100644 index 000000000..a66d93bbc --- /dev/null +++ b/app/host/storage/controller.js @@ -0,0 +1,9 @@ +import Cattle from 'ui/utils/cattle'; + +export default Cattle.CollectionController.extend({ + nonRootVolumes: function() { + return this.get('arrangedContent').filter(function(volume) { + return !volume.get('instanceId') && volume.get('state') !== 'purged'; + }); + }.property('arrangedContent.@each.{instanceId,state}') +}); diff --git a/app/host/storage/route.js b/app/host/storage/route.js new file mode 100644 index 000000000..aac111979 --- /dev/null +++ b/app/host/storage/route.js @@ -0,0 +1,19 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model: function() { + var host = this.modelFor('host'); + var out = []; + return host.followLink('storagePools').then((pools) => { + var promises = pools.map((pool) => { + return pool.followLink('volumes',{include: 'mounts'}).then((volumes) => { + out.pushObjects((volumes||[]).toArray()); + }); + }); + + return Ember.RSVP.all(promises).then(() => { + return out; + }); + }); + } +}); diff --git a/app/host/storage/template.hbs b/app/host/storage/template.hbs index b1696c80c..8d3cbbc84 100644 --- a/app/host/storage/template.hbs +++ b/app/host/storage/template.hbs @@ -1 +1,54 @@ -

Coming soon, replacement for the Volumes tab...

+
+ + + + + + + + + + + {{#each volume in nonRootVolumes itemController="volume"}} + + + + + + + + + + {{else}} + + {{/each}} + +
StateHost PathMounts 
+ + {{volume.displayState}} + + +
+ {{volume.displayUri}} +
+ {{zero-clipboard text=volume.displayUri class="with-clip"}} +
+ {{#if volume.activeMounts.length}} + {{#each mount in volume.activeMounts itemController="mount"}} +
+ {{#link-to "container" mount.instanceId}}{{mount.instance.name}}{{/link-to}}:{{mount.path}} + {{#if (eq mount.permissions "ro")}} + (read-only) + {{/if}} +
+ {{/each}} + {{else}} + None + {{/if}} +
+ {{#if volume.availableActions.length}} + {{resource-actions-menu model=volume choices=volume.availableActions}} + {{/if}} +
This host does not have any containers yet.
+
+ diff --git a/app/initializers/touch.js b/app/initializers/touch.js index c8a67ebe6..636ad71c2 100644 --- a/app/initializers/touch.js +++ b/app/initializers/touch.js @@ -1,5 +1,5 @@ export function initialize(/*container, application*/) { - // Add 'touch' or 'no-touch' to the so CSS can depend on the devicve type. + // Add 'touch' or 'no-touch' to the so CSS can depend on the device type. var body = $('BODY'); if ('ontouchstart' in document.documentElement) diff --git a/app/loadbalancer/hosts/new/controller.js b/app/loadbalancer/hosts/new/controller.js index 4f1b5603e..a872315bd 100644 --- a/app/loadbalancer/hosts/new/controller.js +++ b/app/loadbalancer/hosts/new/controller.js @@ -15,6 +15,7 @@ export default Ember.Controller.extend({ }, save: function() { + this.set('errors', null); this.set('saving',true); var promises = []; var balancer = this.get('model'); diff --git a/app/loadbalancer/hosts/new/route.js b/app/loadbalancer/hosts/new/route.js index 8f15f9edf..9d8cbd03b 100644 --- a/app/loadbalancer/hosts/new/route.js +++ b/app/loadbalancer/hosts/new/route.js @@ -25,6 +25,13 @@ export default OverlayRoute.extend({ this.render({into: 'application', outlet: 'overlay'}); }, + resetController: function (controller, isExisting/*, transition*/) { + if (isExisting) + { + controller.set('errors', null); + } + }, + actions: { cancel: function() { this.transitionTo('loadbalancer.hosts'); diff --git a/app/loadbalancer/targets/new/controller.js b/app/loadbalancer/targets/new/controller.js index 45c83f8d3..560a1338b 100644 --- a/app/loadbalancer/targets/new/controller.js +++ b/app/loadbalancer/targets/new/controller.js @@ -4,6 +4,7 @@ import TargetChoices from 'ui/mixins/target-choices'; export default Ember.Controller.extend(TargetChoices, { error: null, editing: false, + saving: false, actions: { addTargetContainer: function() { @@ -17,6 +18,8 @@ export default Ember.Controller.extend(TargetChoices, { }, save: function() { + this.set('saving',true); + this.set('errors', null); var promises = []; var balancer = this.get('model'); @@ -34,6 +37,24 @@ export default Ember.Controller.extend(TargetChoices, { return Ember.RSVP.all(promises,'Add multiple targets').then(() => { this.send('cancel'); + }).catch((err) => { + if ( err.status === 422 && err.code === 'NotUnique' ) + { + if ( this.get('targetsArray.length') > 1 ) + { + this.set('errors',['This load balancer is already has one or more of the specified targets']); + } + else + { + this.set('errors',['This load balancer already has the specified targets']); + } + } + else + { + this.set('errors', [err]); + } + }).finally(() => { + this.set('saving', false); }); }, }, diff --git a/app/loadbalancer/targets/new/route.js b/app/loadbalancer/targets/new/route.js index 5dcfd9ee8..713236ba1 100644 --- a/app/loadbalancer/targets/new/route.js +++ b/app/loadbalancer/targets/new/route.js @@ -25,6 +25,13 @@ export default OverlayRoute.extend({ this.render({into: 'application', outlet: 'overlay'}); }, + resetController: function (controller, isExisting/*, transition*/) { + if (isExisting) + { + controller.set('errors', null); + } + }, + actions: { cancel: function() { this.goToPrevious(); diff --git a/app/loadbalancer/targets/new/template.hbs b/app/loadbalancer/targets/new/template.hbs index 830a9d27d..59ec0448a 100644 --- a/app/loadbalancer/targets/new/template.hbs +++ b/app/loadbalancer/targets/new/template.hbs @@ -1,5 +1,6 @@

Add Targets

+ {{top-errors errors=errors}}
diff --git a/app/mount/controller.js b/app/mount/controller.js index 5c301b33a..ad8b0d46a 100644 --- a/app/mount/controller.js +++ b/app/mount/controller.js @@ -4,6 +4,15 @@ import Ember from 'ember'; var MountController = Cattle.TransitioningResourceController.extend({ isReadWrite: Ember.computed.equal('permissions','rw'), isReadOnly: Ember.computed.equal('permissions','ro'), + + instance: function() { + var proxy = Ember.ObjectProxy.create({content: {}}); + this.get('store').find('container', this.get('instanceId')).then((container) => { + proxy.set('content', container); + }); + + return proxy; + }.property('instanceId'), }); MountController.reopenClass({ diff --git a/app/styles/zero-clipboard.scss b/app/styles/zero-clipboard.scss index 6b87956b2..99bfd428c 100644 --- a/app/styles/zero-clipboard.scss +++ b/app/styles/zero-clipboard.scss @@ -29,3 +29,13 @@ @extend .text-muted; padding: 0; } + +.zeroclip.with-clip { + display: inline-block +} + +.clip.with-zeroclip { + max-width: calc(100% - 20px); + float: left; + padding-right: 3px +}