Merge pull request #1326 from westlywright/2.0-alpha

2.0 alpha
This commit is contained in:
Vincent Fiduccia 2017-08-25 18:01:04 -07:00 committed by GitHub
commit 049abae695
22 changed files with 334 additions and 127 deletions

View File

@ -21,6 +21,11 @@ export default Ember.Component.extend({
click(e) { click(e) {
var tgt = Ember.$(e.target); var tgt = Ember.$(e.target);
var more = tgt.closest('.more-actions'); var more = tgt.closest('.more-actions');
var offsets = {
y: -1,
x: 2,
mirror: true
};
if ( more && more.length ) { if ( more && more.length ) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -31,7 +36,7 @@ export default Ember.Component.extend({
this.get('resourceActions').set('tooltipActions', false); this.get('resourceActions').set('tooltipActions', false);
} }
this.get('resourceActions').show(this.get('model'), more, this.$()); this.get('resourceActions').show(this.get('model'), more, this.$(), offsets);
} }
}, },

View File

@ -1,10 +1,12 @@
import Ember from 'ember'; import Ember from 'ember';
import NewOrEdit from 'ui/mixins/new-or-edit'; import NewOrEdit from 'ui/mixins/new-or-edit';
import C from 'ui/utils/constants'; import C from 'ui/utils/constants';
import StackState from 'ui/mixins/stack-memory';
export default Ember.Component.extend(NewOrEdit, { export default Ember.Component.extend(NewOrEdit, StackState, {
intl : Ember.inject.service(), intl : Ember.inject.service(),
settings : Ember.inject.service(), settings : Ember.inject.service(),
prefs: Ember.inject.service(),
service : null, service : null,
editing : null, editing : null,
@ -252,6 +254,7 @@ export default Ember.Component.extend(NewOrEdit, {
}, },
doneSaving() { doneSaving() {
this._super(...arguments);
this.send('done'); this.send('done');
}, },
}); });

View File

@ -1,6 +1,7 @@
import Ember from 'ember'; import Ember from 'ember';
import NewOrEdit from 'ui/mixins/new-or-edit'; import NewOrEdit from 'ui/mixins/new-or-edit';
import Errors from 'ui/utils/errors'; import Errors from 'ui/utils/errors';
import StackState from 'ui/mixins/stack-memory';
const HOSTNAME = 'externalhostname'; const HOSTNAME = 'externalhostname';
const IP = 'externalip'; const IP = 'externalip';
@ -15,7 +16,7 @@ function modeToType(mode) {
} }
} }
export default Ember.Component.extend(NewOrEdit, { export default Ember.Component.extend(NewOrEdit, StackState, {
intl: Ember.inject.service(), intl: Ember.inject.service(),
record: null, record: null,
@ -156,6 +157,7 @@ export default Ember.Component.extend(NewOrEdit, {
}, },
doneSaving() { doneSaving() {
this.send('cancel'); this._super(...arguments);
this.send('done');
}, },
}); });

View File

@ -1,37 +1,31 @@
{{~#if project~}} {{~#if project~}}
<li class="dropdown pull-right"> <li class="dropdown pull-right ">
{{#if (gt byCluster.length 1)}} {{#if (gt byCluster.length 1)}}
<ul class="list-unstyled pr-40" style="padding: 4px;"> <div class="cluster-dropdown dropdown-toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.cluster.label'}}" data-toggle="environment">
<li> <span class="text-white clip" >
<a href="#" role="button" class="text-white dropdown-toggle clip" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.cluster.label'}}">
<i class="icon icon-cluster icon-fw"></i> <i class="icon icon-cluster icon-fw"></i>
{{project.cluster.displayName}} {{project.cluster.displayName}}
<span class="sr-only">{{t 'nav.srToggleDropdown'}}</span> <span class="sr-only">{{t 'nav.srToggleDropdown'}}</span>
</a> </span>
</li> <span class="block pl-15 text-small text-muted">
<li class="pl-15 text-small text-muted">
<a href="#" role="button" class="text-white dropdown-toggle clip" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.environment.label'}}">
<i class="{{project.icon}} project-icon icon-fw"></i> <i class="{{project.icon}} project-icon icon-fw"></i>
{{project.displayName}} {{project.displayName}}
<span class="sr-only">{{t 'nav.srToggleDropdown'}}</span> <span class="sr-only">{{t 'nav.srToggleDropdown'}}</span>
</a> </span>
</li>
<i class="icon icon-chevron-down project-chevron ml-10 text-white" style="position: absolute; right: 20px; top: 20px;"></i> <i class="icon icon-chevron-down project-chevron ml-10 text-white" style="position: absolute; right: 20px; top: 20px;"></i>
</ul> </div>
{{else}} {{else}}
<span class="environment-dropdown"> <span class="environment-dropdown dropdown-toggle" aria-role="button" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.environment.label'}}" data-toggle="environment">
<i class="{{project.icon}} project-icon icon-fw"></i> <i class="{{project.icon}} project-icon icon-fw"></i>
<a href="#" role="button" class="dropdown-toggle clip" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.environment.label'}}"> <span class="text-white clip" >
{{project.displayName}} {{project.displayName}}
<span class="sr-only">{{t 'nav.srToggleDropdown'}}</span> <span class="sr-only">{{t 'nav.srToggleDropdown'}}</span>
</a> </span>
<i class="icon icon-chevron-down project-chevron"></i> <i class="icon icon-chevron-down project-chevron"></i>
</span> </span>
{{/if}} {{/if}}
<ul class="dropdown-menu project-menu" role="menu" data-dropdown-id="enviroment"> <ul class="dropdown-menu dropdown-menu-right project-menu" role="menu" data-dropdown-id="environment">
{{#if (gt byCluster.length 1)}} {{#if (gt byCluster.length 1)}}
{{#each byCluster as |entry|}} {{#each byCluster as |entry|}}
<li class="{{if entry.system.active 'active selected'}}"> <li class="{{if entry.system.active 'active selected'}}">

View File

@ -48,6 +48,14 @@ export default Ember.Component.extend(HoverDropdown, {
}, },
}, },
willRender() {
if (Ember.$('BODY').hasClass('touch') && Ember.$('header > nav').hasClass('nav-open')) {
Ember.run.later(() => {
Ember.$('header > nav').removeClass('nav-open');
});
}
},
init() { init() {
this._super(...arguments); this._super(...arguments);
this.get('intl.locale'); this.get('intl.locale');

View File

@ -94,7 +94,7 @@
<ul class="nav-user dropdown"> <ul class="nav-user dropdown">
<li> <li>
<a class="btn dropdown-toggle" style="height: 51px; padding-top: 8px;" role="button" aria-haspopup="true" aria-expanded="false" aria-label={{t 'nav.user.label' username=session.user}}> <a class="btn dropdown-toggle" style="height: 51px; padding-top: 8px;" role="button" aria-haspopup="true" aria-expanded="false" aria-label="{{t 'nav.user.label' username=session.user}}" data-toggle="header-user-menu" >
{{#if accessEnabled}} {{#if accessEnabled}}
{{identity-avatar link=false identity=access.identity}} {{identity-avatar link=false identity=access.identity}}
{{else}} {{else}}

View File

@ -452,7 +452,6 @@ export default Ember.Component.extend(Sortable, StickyHeader, {
} }
this.set('prevNode', node); this.set('prevNode', node);
e.stopPropagation();
}, },
nodesBetween(a,b) { nodesBetween(a,b) {

View File

@ -10,6 +10,13 @@ let timerObj = null;
let dropdown = null; let dropdown = null;
export default Ember.Mixin.create({ export default Ember.Mixin.create({
willRender(){
if (Ember.$('BODY').hasClass('touch')) {
Ember.run.later(() => {
this.clearHeaderMenus();
}, DROPDOWNCLOSETIMER);
}
},
didInsertElement: function() { didInsertElement: function() {
let $body = Ember.$('BODY'); let $body = Ember.$('BODY');
@ -24,7 +31,7 @@ export default Ember.Mixin.create({
} }
}); });
this.$('#environment-dropdown').on('touchstart', (e) => { this.$('[data-toggle="header-user-menu"]').on('touchstart', (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.touchHandler(e); this.touchHandler(e);
@ -111,7 +118,20 @@ export default Ember.Mixin.create({
}, },
enterHandler(e) { enterHandler(e) {
let anchor = Ember.$(e.currentTarget).find('a:first'); let anchor = Ember.$(e.currentTarget).find('.dropdown-toggle');
let dataTarget = anchor.data('toggle') ? anchor.data('toggle') : null;
let findTarget = dataTarget ? `ul[data-dropdown-id ="${dataTarget}"]` : 'ul';
let offset = null;
if (anchor.data('offset-x') || anchor.data('offset-y')) {
offset = {};
if (anchor.data('offset-x')) {
offset.x = anchor.data('offset-x');
}
if (anchor.data('offset-y')) {
offset.y = anchor.data('offset-y');
}
}
Ember.run.cancel(timerObj); Ember.run.cancel(timerObj);
@ -126,15 +146,15 @@ export default Ember.Mixin.create({
dropdown = Ember.$(e.currentTarget).find('ul'); dropdown = Ember.$(e.currentTarget).find('ul');
if (dropdown) { if (dropdown) {
this.showMenu(anchor, dropdown); this.showMenu(anchor, dropdown, offset);
} }
} }
} else { // no dropdown open } else { // no dropdown open
dropdown = Ember.$(e.currentTarget).find('ul'); dropdown = Ember.$(e.currentTarget).find(findTarget);
if (dropdown) { if (dropdown) {
this.showMenu(anchor, dropdown); this.showMenu(anchor, dropdown, offset);
} }
} }
@ -224,15 +244,10 @@ export default Ember.Mixin.create({
} }
}, },
showMenu: function(el, drpd) { showMenu: function(el, drpd, offset) {
let body = Ember.$('BODY');
if (body.hasClass('touch')) {
Ember.$('BODY').addClass('nav-dropdown-open');
}
drpd.addClass('invisible'); drpd.addClass('invisible');
drpd.addClass('block'); drpd.addClass('block');
positionDropdown(drpd, el, drpd.hasClass('dropdown-menu-right')); positionDropdown(drpd, el, drpd.hasClass('dropdown-menu-right'), offset);
drpd.removeClass('invisible'); drpd.removeClass('invisible');
if (el.attr('aria-expanded')) { if (el.attr('aria-expanded')) {
@ -241,10 +256,6 @@ export default Ember.Mixin.create({
}, },
clearHeaderMenus: function() { clearHeaderMenus: function() {
let body = Ember.$('BODY');
if (body.hasClass('touch')) {
Ember.$('BODY').removeClass('nav-dropdown-open');
}
const navbar = Ember.$(PARENT); const navbar = Ember.$(PARENT);
dropdown = null; dropdown = null;

View File

@ -0,0 +1,23 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
export default Ember.Mixin.create({
prefs: Ember.inject.service(),
stack: null,
init() {
this._super(...arguments);
this.setStack();
},
setStack() {
let stackId = this.get(`prefs.${C.PREFS.LAST_STACK}`) || null;
if (stackId) {
this.set('stack', this.get('store').getById('stack', stackId));
}
},
doneSaving() {
if (this.get('stack')) {
this.set(`prefs.${C.PREFS.LAST_STACK}`, this.get('stack.id'));
}
this.send('done');
},
});

View File

@ -2,6 +2,7 @@ import Ember from 'ember';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
application: Ember.inject.controller(), application: Ember.inject.controller(),
projects: Ember.inject.service(),
service: Ember.computed.alias('model.service'), service: Ember.computed.alias('model.service'),
rules: Ember.computed.alias('service.lbConfig.portRules'), rules: Ember.computed.alias('service.lbConfig.portRules'),

View File

@ -17,6 +17,9 @@ export default Ember.Route.extend({
if (model.get('service.initPorts')) { if (model.get('service.initPorts')) {
model.get('service').initPorts(); model.get('service').initPorts();
} }
if (model.get('service.stackId')) {
model.set('stack', this.get('store').getById('stack', model.get('service.stackId')));
}
}, },
getServiceLogs(serviceId) { getServiceLogs(serviceId) {

View File

@ -22,6 +22,28 @@
<section> <section>
<div class="row banner bg-info basics"> <div class="row banner bg-info basics">
{{#if service.externalIpAddresses}}
<div class="inline-block">
<label class="text-muted ml-15">{{t 'servicePage.external.externalIp' count=service.externalIpAddresses.length}}</label>
{{join-array service.externalIpAddresses}}
</div>
{{else if service.hostname}}
<div class="inline-block">
<label class="text-muted">{{t 'servicePage.external.externalHostname'}} </label> {{service.hostname}}
</div>
{{else if service.selector}}
<div class="inline-block">
<label class="text-muted">{{t 'servicePage.selector.label'}} </label> {{service.selector}}
</div>
{{/if}}
{{#if service.hasImage}}
<div class="inline-block">
<label class="acc-label p-0">{{t 'servicePage.multistat.image'}}</label>
{{fixedLaunchConfig.image}} {{copy-to-clipboard clipboardText=fixedLaunchConfig.image size="small"}}
</div>
{{/if}}
{{#if fixedLaunchConfig.memoryReservation}} {{#if fixedLaunchConfig.memoryReservation}}
<div class="inline-block"> <div class="inline-block">
<label class="acc-label p-0">{{t 'containersPage.containerPage.infoMultiStats.memoryReservation.labelText'}}</label> <label class="acc-label p-0">{{t 'containersPage.containerPage.infoMultiStats.memoryReservation.labelText'}}</label>
@ -57,27 +79,14 @@
</div> </div>
{{/if}} {{/if}}
{{#if service.hasImage}}
{{#if model.stack}}
<div class="inline-block"> <div class="inline-block">
<label class="acc-label p-0">{{t 'servicePage.multistat.image'}}</label> <label class="acc-label p-0">{{t 'generic.stack'}}:</label>
{{fixedLaunchConfig.image}} {{copy-to-clipboard clipboardText=fixedLaunchConfig.image size="small"}} {{#link-to "stack" projects.current.id model.stack.id}}{{model.stack.displayName}}{{/link-to}}
</div> </div>
{{/if}} {{/if}}
{{#if service.externalIpAddresses}}
<div class="inline-block">
<label class="text-muted ml-15">{{t 'servicePage.external.externalIp' count=service.externalIpAddresses.length}}</label>
{{join-array service.externalIpAddresses}}
</div>
{{else if service.hostname}}
<div class="inline-block">
<label class="text-muted">{{t 'servicePage.external.externalHostname'}} </label> {{service.hostname}}
</div>
{{else if service.selector}}
<div class="inline-block">
<label class="text-muted">{{t 'servicePage.selector.label'}} </label> {{service.selector}}
</div>
{{/if}}
</div> </div>
</section> </section>

View File

@ -8,7 +8,16 @@ export default Ember.Service.extend({
actionToggle : null, actionToggle : null,
actionMenu : null, actionMenu : null,
show: function(model,trigger,toggle) { // offset is a parameter that we need to open in our api, it allows us to pass a
// positial x/y offset to the position-calculator library
// http://tlindig.github.io/position-calculator/
// itemOffset: {
// y: -1,
// x: 2,
// mirror: true
// },
show: function(model,trigger,toggle, offset) {
if (this.get('open') && this.get('actionMenu')) { if (this.get('open') && this.get('actionMenu')) {
this.hide(); this.hide();
} }
@ -52,7 +61,7 @@ export default Ember.Service.extend({
this.set('open',true); this.set('open',true);
// Delay ensure it works in firefox // Delay ensure it works in firefox
Ember.run.next(() => { Ember.run.next(() => {
positionDropdown($menu, trigger, true); positionDropdown($menu, trigger, true, offset);
$('#resource-actions-first')[0].focus(); $('#resource-actions-first')[0].focus();
$menu.css('visibility','visible'); $menu.css('visibility','visible');
}); });

View File

@ -1,5 +1,6 @@
import Ember from 'ember'; import Ember from 'ember';
import { headersWithHost as containerHeaders } from 'ui/components/container-table/component'; import { headersWithHost as containerHeaders } from 'ui/components/container-table/component';
import { searchFields as containerSearchFields } from 'ui/components/container-dots/component';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
prefs: Ember.inject.service(), prefs: Ember.inject.service(),
@ -99,11 +100,68 @@ export default Ember.Controller.extend({
name: 'scale', name: 'scale',
sort: ['scale:desc','isGlobalScale:desc','displayName'], sort: ['scale:desc','isGlobalScale:desc','displayName'],
searchField: null, searchField: null,
width: 100,
translationKey: 'stacksPage.table.scale', translationKey: 'stacksPage.table.scale',
classNames: 'text-center', classNames: 'text-center',
width: 100
}, },
], ],
storageSortBy: 'state',
storageHeaders: [
{
name: 'expand',
sort: false,
searchField: null,
width: 30
},
{
name: 'state',
sort: ['stateSort','displayName'],
searchField: 'displayState',
translationKey: 'generic.state',
width: 120
},
{
name: 'name',
sort: ['displayName','id'],
searchField: 'displayName',
translationKey: 'generic.name',
},
{
name: 'mounts',
sort: ['mounts.length','displayName','id'],
translationKey: 'volumesPage.mounts.label',
searchField: null,
width: 100,
},
{
name: 'scope',
sort: ['scope'],
translationKey: 'volumesPage.scope.label',
width: 120
},
{
name: 'driver',
sort: ['driver','displayName','id'],
searchField: 'displayType',
translationKey: 'volumesPage.driver.label',
width: 150
},
],
extraSearchFields: ['id:prefix','displayIp:ip'],
extraSearchSubFields: containerSearchFields,
rows: Ember.computed('instances.[]', 'scalingGroups.[]', function() {
let out = [];
let containers = this.get('instances');
let scalinggroups = this.get('scalingGroups');
return out.concat(containers, scalinggroups);
}),
containerStats: Ember.computed('instances.[]', 'scalingGroups.[]', function() {
let containerLength = this.get('instances.length') || 0;
let scalingGroupsLength = this.get('scalingGroups.length') || 0;
return containerLength += scalingGroupsLength;
}),
getType(ownType, real=true) { getType(ownType, real=true) {
return this.get('model.services').filter((service) => { return this.get('model.services').filter((service) => {

View File

@ -1,10 +1,27 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ export default Ember.Route.extend({
parentRoute: 'stack',
resetController: function (controller, isExisting/*, transition*/) { resetController: function (controller, isExisting/*, transition*/) {
if (isExisting) if (isExisting)
{ {
controller.set('showAddtlInfo', false); controller.set('showAddtlInfo', false);
} }
}, },
model(/* params, transition */) {
let model = this.modelFor(this.get('parentRoute'));
return this.get('store').findAll('volume').then((volumes) => {
return this.get('store').findAll('volumetemplate').then((volumeTemplates) => {
let volOut = [];
model.volumes = volOut.concat(volumes.filterBy('stackId', model.get('stack.id')), volumeTemplates.filterBy('stackId', model.get('stack.id')));
return model;
});
});
}
}); });

View File

@ -1,67 +1,63 @@
{{stack-header model=model.stack all=model.all.stacks}} {{stack-header model=model.stack all=model.all.stacks}}
{{#if model.stack.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=model.stack.description}}
{{/if}}
<section> <section>
{{#accordion-list as |al expandFn| }} {{#accordion-list as |al expandFn| }}
{{#accordion-list-item {{#accordion-list-item
title=(t 'stackPage.containers.header') title=(t 'stackPage.containers.header')
detail=(t 'stackPage.containers.detail') detail=(t 'stackPage.containers.detail')
status=(t 'stackPage.containers.status' count=instances.length) status=(t 'stackPage.containers.status' count=instances.length)
statusClass=(if instances.length 'bg-success' 'text-muted') statusClass=(if containerStats 'bg-success' 'text-muted')
expandOnInit=true
expandAll=al.expandAll expandAll=al.expandAll
expand=(action expandFn) expand=(action expandFn)
componentName='container-table' componentName='container-table'
as | parent | as | parent |
}} }}
{{component parent.intent {{#sortable-table
body=instances tableClassNames="double-rows"
search=true
sortBy=sortBy
stickyHeader=false
bulkActions=true
showHost=false
}}
{{/accordion-list-item}}
{{#accordion-list-item
title=(t 'stackPage.scalingGroups.header')
detail=(t 'stackPage.scalingGroups.detail')
status=(t 'stackPage.scalingGroups.status' count=scalingGroups.length)
statusClass=(if scalingGroups.length 'bg-success' 'text-muted')
expandAll=al.expandAll
expand=(action expandFn)
componentName='sortable-table'
as | parent |
}}
{{#component parent.intent
body=scalingGroups
bulkActions=true
classNames="grid sortable-table" classNames="grid sortable-table"
fullRows=true body=rows
headers=sgHeaders
pagingLabel="pagination.service"
searchText=searchText searchText=searchText
sortBy=sortBy sortBy=sortBy
stickyHeader=false bulkActions=true
subHeaders=containerHeaders
subRows=true subRows=true
fullRows=true
pagingLabel="pagination.containerService"
subSearchField="instances" subSearchField="instances"
as |sortable kind serv dt|}} extraSearchFields=extraSearchFields
extraSearchSubFields=extraSearchSubFields
headers=sgHeaders as |sortable kind inst dt|}}
{{#if (eq kind "row")}} {{#if (eq kind "row")}}
{{service-row {{#if (eq inst.baseType "instance")}}
canExpand=true {{container-row
expanded=(array-includes expandedInstances serv.id) model=inst
dt=dt
showHost=true
expandPlaceholder=true
scalePlaceholder=true
fullColspan=sortable.fullColspan fullColspan=sortable.fullColspan
model=serv }}
{{else}}
{{service-row
model=inst
toggle=(action "toggleExpand" inst.id)
expanded=(array-includes expandedInstances inst.id)
searchText=searchText searchText=searchText
showInstanceCount=false
subMatches=sortable.subMatches subMatches=sortable.subMatches
toggle=(action "toggleExpand" serv.id) fullColspan=sortable.fullColspan
dt=dt dt=dt
}} }}
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'containersPage.table.noMatch'}}</td></tr>
{{/if}} {{/if}}
{{/component}} {{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'containersPage.table.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted lacsso pt-20 pb-20">{{t 'containersPage.table.noData'}}</td></tr>
{{/if}}
{{/sortable-table}}
{{/accordion-list-item}} {{/accordion-list-item}}
{{#accordion-list-item {{#accordion-list-item
@ -144,5 +140,48 @@
{{/if}} {{/if}}
{{/component}} {{/component}}
{{/accordion-list-item}} {{/accordion-list-item}}
{{#accordion-list-item
title=(t 'stackPage.volumesTab.header')
detail=(t 'stackPage.volumesTab.detail')
status=(t 'pagination.volume' pages=1 count=(or model.volumes.length 0))
statusClass=(if model.volumes.length 'bg-success' 'text-muted')
expandAll=al.expandAll
expand=(action expandFn)
componentName='sortable-table'
as | parent |
}}
{{#component parent.intent
body=model.volumes
bulkActions=true
classNames="grid sortable-table"
fullRows=true
isVisible=parent.expanded
pagingLabel="pagination.volume"
searchText=searchText
sortBy=sortBy
stickyHeader=false
subHeaders=containerHeaders
subRows=true
subSearchField="instances"
headers=storageHeaders as |sortable kind mount dt|
}}
{{#if (eq kind "row")}}
{{volume-row
model=mount
toggle=(action "toggleExpand" mount.id)
expanded=(array-includes expandedInstances mount.id)
searchText=searchText
subMatches=sortable.subMatches
fullColspan=sortable.fullColspan
dt=dt
}}
{{else if (eq kind "nomatch")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'stackPage.volumesTab.table.noMatch'}}</td></tr>
{{else if (eq kind "norows")}}
<tr><td colspan="{{sortable.fullColspan}}" class="text-center text-muted pt-20 pb-20">{{t 'stackPage.volumesTab.table.noData'}}</td></tr>
{{/if}}
{{/component}}
{{/accordion-list-item}}
{{/accordion-list}} {{/accordion-list}}
</section> </section>

View File

@ -42,8 +42,15 @@ $user-btn : darken($header, 10%) !default;
padding: 0; padding: 0;
} }
.cluster-dropdown {
padding: 4px 40px 4px 4px;
@extend .clip;
@extend .hand;
}
.environment-dropdown { .environment-dropdown {
padding: 0 10px; padding: 0 10px;
@extend .clip;
@extend .hand;
a { a {
color: white; color: white;

View File

@ -171,7 +171,6 @@
//nav //nav
.responsive-nav { .responsive-nav {
position: relative; position: relative;
overflow: hidden;
background-color: $header; background-color: $header;
} }
@ -202,7 +201,9 @@
} }
/* Class added via JS when toggled open */ /* Class added via JS when toggled open */
.nav-open { .nav-open {
overflow: visible; .nav-list {
max-height: 1000px;
}
} }
/* NAV LIST*/ /* NAV LIST*/
@ -213,6 +214,13 @@
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
width: 100%; width: 100%;
max-height: 0;
overflow: hidden;
-webkit-transition: max-height .5s;
-moz-transition: max-height .5s;
-ms-transition: max-height .5s;
-o-transition: max-height .5s;
transition: max-height .5s;
} }
@ -284,7 +292,7 @@
background-size: 75%; background-size: 75%;
margin-left: 10px; margin-left: 10px;
} }
.page-header NAV .dropdown-menu { .page-header NAV .nav-list .dropdown-menu {
background: transparent; background: transparent;
box-shadow: none; box-shadow: none;
max-width: 100%; max-width: 100%;

View File

@ -1,5 +1,4 @@
.dot { .dot {
float: left;
margin: 0; margin: 0;
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;

View File

@ -6,7 +6,7 @@ export function resizeDropdown(event) {
return positionDropdown($item, target, right); return positionDropdown($item, target, right);
} }
export function positionDropdown(menu, trigger, right) { export function positionDropdown(menu, trigger, right, offset) {
// https://github.com/twbs/bootstrap/issues/10756#issuecomment-41041800 // https://github.com/twbs/bootstrap/issues/10756#issuecomment-41041800
var direction = (right === true ? 'right' : 'left'); var direction = (right === true ? 'right' : 'left');
var $menu = $(menu); var $menu = $(menu);
@ -17,19 +17,20 @@ export function positionDropdown(menu, trigger, right) {
left: 0 left: 0
}); });
// calculate new position let pco = {
var calculator = new $.PositionCalculator({
item: $menu, item: $menu,
target: trigger, target: trigger,
itemAt: 'top ' + direction, itemAt: 'top ' + direction,
itemOffset: {
y: 3,
x: 0,
mirror: true
},
targetAt: 'bottom ' + direction, targetAt: 'bottom ' + direction,
flip: 'both' flip: 'both'
}); };
if (offset) {
pco.itemOffset = offset;
}
// calculate new position
var calculator = new $.PositionCalculator(pco);
var posResult = calculator.calculate(); var posResult = calculator.calculate();
// set new position // set new position

View File

@ -7,17 +7,19 @@ export default Ember.Route.extend({
}, },
}, },
model: function(params) { model: function(params) {
let out = Ember.Object.create({ let out = Ember.Object.create({
volume: this.get('store').getById(params.type, params.volume_id) volume: this.get('store').getById(params.type, params.volume_id)
}); });
if (out.volume.stackId) { if (out.volume.stackId) {
out.stack = this.controllerFor('volume').set('stack', this.get('store').getById('stack', out.volume.stackId)); out.stack = this.get('store').getById('stack', out.volume.stackId);
} }
if (out.volume.hostId) { if (out.volume.hostId) {
out.host = this.controllerFor('volume').set('host', this.get('store').getById('host', out.volume.hostId)); out.host = this.get('store').getById('host', out.volume.hostId);
} }
return out; return out;
}, },
}); });

View File

@ -922,7 +922,7 @@ stackPage:
backLink: Back to all stacks backLink: Back to all stacks
containers: containers:
header: Containers header: Containers
detail: Standalone Containers that are not part of a Service or Load Balancer detail: Standalone Containers and Services contained in this stack
status: | status: |
{count, plural, {count, plural,
=0 {No containers} =0 {No containers}
@ -956,6 +956,15 @@ stackPage:
=1 {# container} =1 {# container}
other {# containers} other {# containers}
} }
volumesTab:
header: Volumes
detail: 'These properties show the volumes attached to your container.'
table:
path: Mount Point
shared: Shared With
writable: Writable
noData: This stack has no volumes mounted
noMatch: No volumes match the current search
newStack: newStack:
header: Import Compose.yml header: Import Compose.yml