Update remaining details pages (#813)

update machine drivers (rancher/rancher#5156)
This commit is contained in:
Westly Wright 2016-08-11 13:33:28 -07:00 committed by Vincent Fiduccia
parent feb208bab0
commit 8aff75f902
8 changed files with 370 additions and 252 deletions

View File

@ -53,15 +53,7 @@ export default Ember.Controller.extend(Sortable, {
this.get('store').request({url: template.versionLinks[template.defaultVersion]}).then((driver) =>{ this.get('store').request({url: template.versionLinks[template.defaultVersion]}).then((driver) =>{
let newDriver = { let newDriver = this.createNewDriver(driver);
type : 'machineDriver',
description : (driver.description || null),
checksum : (driver.files.checksum||'').trim() || null,
uiUrl : (driver.files.uiUrl||'').trim() || null,
url : (driver.files.url||'').trim() || null,
externalId : driver.id,
activateOnCreate: true,
};
this.get('userStore').createRecord(newDriver).save().then((result) => { this.get('userStore').createRecord(newDriver).save().then((result) => {
this.get('model.drivers').pushObject(result); this.get('model.drivers').pushObject(result);
@ -76,14 +68,11 @@ export default Ember.Controller.extend(Sortable, {
let templateVersion = version; let templateVersion = version;
this.set('upgrading', true); this.set('upgrading', true);
// find latest version of driver // find latest version of driver
this.get('store').request({url: this.get('app.catalogEndpoint')+'/templateversions/'+driver.externalId}).then((template) => { this.get('store').request({url: this.get('app.catalogEndpoint')+'/templateversions/'+driver.externalId}).then((template) => {
this.get('store').request({url: template.upgradeVersionLinks[templateVersion]}).then((item) => { this.get('store').request({url: template.upgradeVersionLinks[templateVersion]}).then((item) => {
driver.setProperties({ driver.setProperties(this.createNewDriver(item));
url : item.files.url,
checksum : item.files.checksum,
uiUrl : item.files.uiUrl,
});
driver.save().then(() => { driver.save().then(() => {
this.set('upgrading', false); this.set('upgrading', false);
}).catch((err) => { }).catch((err) => {
@ -95,7 +84,19 @@ export default Ember.Controller.extend(Sortable, {
} }
}, },
sortableContent: Ember.computed('model.drivers.@each', 'model.catalogDrivers.@each', function() { createNewDriver: function(driver) {
return {
type : 'machineDriver',
description : (driver.description || null),
checksum : (driver.files.checksum||'').trim() || null,
uiUrl : (driver.files.uiUrl||'').trim() || null,
url : (driver.files.url||'').trim() || null,
externalId : driver.id,
activateOnCreate: true,
};
},
sortableContent: Ember.computed('model.drivers.[]', 'model.catalogDrivers.[]', function() {
// possibly add some search here // possibly add some search here
let cDrivers = this.get('model.catalogDrivers.catalog'); let cDrivers = this.get('model.catalogDrivers.catalog');
let drivers = this.get('model.drivers.content'); let drivers = this.get('model.drivers.content');
@ -109,18 +110,25 @@ export default Ember.Controller.extend(Sortable, {
let extId = driver.externalId.split(':'); let extId = driver.externalId.split(':');
extId = extId.slice(0, extId.length - 1).join(':'); extId = extId.slice(0, extId.length - 1).join(':');
driver.set('fullVersionInfo', null);
if (cDriver.id === extId) { if (cDriver.id === extId) {
this.get('store').request({url: `${this.get('app.catalogEndpoint')}/templateversions/${cDriver.id}`}).then((fullUpdgradeInfo) => {
driver.set('fullVersionInfo', fullUpdgradeInfo.versionLinks);
driver.set('currentVersion', getCurrentVersion(fullUpdgradeInfo.versionLinks, driver.externalId));
this.get('store').request({url: this.get('app.catalogEndpoint')+'/templateversions/'+ driver.externalId}).then((upgradeInfo) => { this.get('store').request({url: this.get('app.catalogEndpoint')+'/templateversions/'+ driver.externalId}).then((upgradeInfo) => {
if (upgradeInfo.id === driver.externalId) {
return false;
}
if (upgradeInfo.upgradeVersionLinks && Object.keys(upgradeInfo.upgradeVersionLinks).length) { if (upgradeInfo.upgradeVersionLinks && Object.keys(upgradeInfo.upgradeVersionLinks).length) {
driver.set('upgradeAvailable', true); driver.set('upgradeAvailable', true);
driver.set('upgradeVersionLinks', upgradeInfo.upgradeVersionLinks);
} }
});
return true; return true;
});
});
} }
} }
@ -132,6 +140,14 @@ export default Ember.Controller.extend(Sortable, {
} }
}); });
var getCurrentVersion = function(driverList, externalId) {
for (var key in driverList) {
if (driverList[key].indexOf(externalId) > -1) {
return key;
}
}
};
newContent = newContent.concat(drivers); newContent = newContent.concat(drivers);
return newContent; return newContent;

View File

@ -40,9 +40,10 @@
<div class="machine-catalog-icon generic"></div> <div class="machine-catalog-icon generic"></div>
{{/if}} {{/if}}
</div> </div>
<h5 class="r-mb10">{{driver.name}}</h5> <h5>{{driver.name}}</h5>
{{#if driver.upgradeAvailable}} {{#if driver.fullVersionInfo}}
<div class="btn-group r-mt5"> <div class="btn-group r-mt5">
{{#if driver.upgradeAvailable}}
<button type="button" class="btn btn-warning btn-xs"> <button type="button" class="btn btn-warning btn-xs">
{{#if upgrading}} {{#if upgrading}}
<i class="icon icon-spinner icon-spin"></i> {{t 'machinePage.upgrading'}} <i class="icon icon-spinner icon-spin"></i> {{t 'machinePage.upgrading'}}
@ -50,14 +51,25 @@
{{t 'machinePage.upgradeAvailable'}} {{t 'machinePage.upgradeAvailable'}}
{{/if}} {{/if}}
</button> </button>
{{else}}
<button type="button" class="btn btn-warning btn-xs">
{{#if upgrading}}
<i class="icon icon-spinner icon-spin"></i> {{t 'machinePage.upgrading'}}
{{else}}
{{driver.currentVersion}}
{{/if}}
</button>
{{/if}}
<button type="button" class="btn btn-warning btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="true"> <button type="button" class="btn btn-warning btn-xs dropdown-toggle" data-toggle="dropdown" aria-expanded="true">
<i class="icon icon-fw icon-chevron-down"></i> <i class="icon icon-fw icon-chevron-down"></i>
<span class="sr-only">{{t 'nav.srToggleDropdown'}}</span> <span class="sr-only">{{t 'nav.srToggleDropdown'}}</span>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right" role="menu"> <ul class="dropdown-menu dropdown-menu-right r-p10" role="menu">
{{!-- each over versions --}} {{!-- each over versions --}}
{{#each-in driver.upgradeVersionLinks as |version path|}} {{#each-in driver.fullVersionInfo as |version path|}}
<li class='hand' {{action "upgradeDriver" driver version path}}>{{version}}</li> <li class='hand r-mt5 r-mb5' {{action "upgradeDriver" driver version path}}>
<span class="{{if (eq driver.currentVersion version) 'btn-warning'}}">{{version}}</span>
</li>
{{/each-in}} {{/each-in}}
</ul> </ul>
</div> </div>

View File

@ -1,8 +1,3 @@
{{#if showMultiStat}}
<div class="container-multi-stat">
{{yield}}
</div>
{{/if}}
{{#if showGraphs}} {{#if showGraphs}}
<div class="row"> <div class="row">
<div class="row-same-height row-full-height"> <div class="row-same-height row-full-height">

View File

@ -71,7 +71,7 @@
</div> </div>
<div class="col-md-9"> <div class="col-md-9">
<div class="row"> <div class="row">
{{info-multi-stats model=model linkName="containerStats" single=true showMultiStat=false}} {{info-multi-stats model=model linkName="containerStats" single=true}}
</div> </div>
<div class="row r-p15"> <div class="row r-p15">
<ul class="nav nav-tabs nav-tabs-well shadowed"> <ul class="nav nav-tabs nav-tabs-well shadowed">

View File

@ -17,94 +17,113 @@
</section> </section>
<section> <section>
{{#info-multi-stats model=host linkName="hostStats" single=true}} <div class="row">
<div class="container-flex bordered"> <div class="col-md-3 rm-mb15">
<div class="col-flex"> {{#if host.description}}
<label>{{t 'hostsPage.hostPage.infoMultiStats.ip'}}</label> <div>
{{format-ip ip=host.displayIp showCopy=true}} <label>{{t 'hostsPage.hostPage.infoMultiStats.description'}} </label>
<div>{{host.description}}</div>
</div> </div>
<div class="col-flex"> <hr>
{{#if host.cpuBlurb}} {{/if}}
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.ip'}}</label>
<div>{{format-ip ip=host.displayIp showCopy=true}}</div>
</div>
<hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.cpu'}}</label> <label>{{t 'hostsPage.hostPage.infoMultiStats.cpu'}}</label>
<div>
{{host.cpuBlurb}} {{host.cpuBlurb}}
{{#if host.cpuTooltip}} {{#if host.cpuTooltip}}
{{#tooltip-element type='tooltip-basic' model=host tooltipTemplate='tooltip-cpu'}} {{#tooltip-element type='tooltip-basic' model=host tooltipTemplate='tooltip-cpu'}}
<i class="icon icon-info"></i> <i class="icon icon-info"></i>
{{/tooltip-element}} {{/tooltip-element}}
{{/if}} {{/if}}
{{/if}}
</div> </div>
<div class="col-flex"> </div>
{{#if host.memoryBlurb}}
<hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.memory'}}</label> <label>{{t 'hostsPage.hostPage.infoMultiStats.memory'}}</label>
{{host.memoryBlurb}} {{#if host.memoryBlurb}}
<div>{{host.memoryBlurb}}</div>
{{else}} {{else}}
<span class="text-muted">{{t 'generic.unknown'}}</span> <div class="text-muted">{{t 'generic.unknown'}}</div>
{{/if}} {{/if}}
</div> </div>
<div class="col-flex">
{{#if host.diskBlurb}} <hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.storage'}}</label> <label>{{t 'hostsPage.hostPage.infoMultiStats.storage'}}</label>
{{#if host.diskBlurb}}
{{#if host.diskDetail}} {{#if host.diskDetail}}
{{#each host.diskDetail as |disk|}} {{#each host.diskDetail as |disk|}}
<span style="display: inline-block; padding-right: 10px;">{{disk.value}} <div>{{disk.value}}
{{#tooltip-element type='tooltip-basic' tooltipTemplate='tooltip-basic-literal' model=disk}} {{#tooltip-element type='tooltip-basic' tooltipTemplate='tooltip-basic-literal' model=disk}}
<i class="icon icon-info"></i> <i class="icon icon-info"></i>
{{/tooltip-element}} {{/tooltip-element}}
</span> </div>
{{/each}} {{/each}}
{{else}} {{else}}
{{host.diskBlurb}} (total) <div>{{host.diskBlurb}} (total)</div>
{{/if}} {{/if}}
{{else}} {{else}}
<span class="text-muted">{{t 'generic.unknown'}}</span> <div class="text-muted">{{t 'generic.unknown'}}</div>
{{/if}} {{/if}}
</div> </div>
</div>
<div class="container-flex bordered"> <hr>
<div class="col-flex">
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.provider.labelText'}}</label> <label>{{t 'hostsPage.hostPage.infoMultiStats.provider.labelText'}}</label>
{{#if host.machine}} {{#if host.machine}}
{{host.machine.driver}} <div>{{host.machine.driver}}</div>
{{else}} {{else}}
<span class="text-muted">{{t 'hostsPage.hostPage.infoMultiStats.provider.noHost'}}</span> <div class="text-muted">{{t 'hostsPage.hostPage.infoMultiStats.provider.noHost'}}</div>
{{/if}} {{/if}}
</div> </div>
<div class="col-flex">
{{#if host.info.osInfo.kernelVersion}}
<label>{{t 'hostsPage.hostPage.infoMultiStats.kernel'}}</label>
{{host.info.osInfo.kernelVersion}}
{{/if}}
</div>
<div class="col-flex">
{{#if host.dockerBlurb}}
<label>{{t 'hostsPage.hostPage.infoMultiStats.docker'}}</label>
{{host.dockerBlurb}}
{{else}}
<span class="text-muted">{{t 'generic.unknown'}}</span>
{{/if}}
</div>
<div class="col-flex">
{{#if host.osDetail}}
<label>{{t 'hostsPage.hostPage.infoMultiStats.os'}}</label>
{{host.osDetail}}
{{else}}
<span class="text-muted">{{t 'generic.unknown'}}</span>
{{/if}}
</div>
</div>
{{#if host.description}}
<div class="row multi-stats">
<div class="col-xs-12 description">
<label>{{t 'hostsPage.hostPage.infoMultiStats.description'}} </label>
<span>{{host.description}}</span>
</div>
</div>
{{/if}}
{{/info-multi-stats}}
</section>
<section> <hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.kernel'}}</label>
<div>{{host.info.osInfo.kernelVersion}}</div>
</div>
<hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.docker'}}</label>
{{#if host.dockerBlurb}}
<div>{{host.dockerBlurb}}</div>
{{else}}
<div class="text-muted">{{t 'generic.unknown'}}</div>
{{/if}}
</div>
<hr>
<div>
<label>{{t 'hostsPage.hostPage.infoMultiStats.os'}}</label>
{{#if host.osDetail}}
<div>{{host.osDetail}}</div>
{{else}}
<div class="text-muted">{{t 'generic.unknown'}}</div>
{{/if}}
</div>
</div>
<div class="col-md-9">
<div class="row">
{{info-multi-stats model=host linkName="hostStats" single=true}}
</div>
<div class="row r-p15">
<ul class="nav nav-tabs nav-tabs-well shadowed" role="tablist" style="display:inline-block;"> <ul class="nav nav-tabs nav-tabs-well shadowed" role="tablist" style="display:inline-block;">
{{#link-to "host.containers" tagName="li" href=false}}<a href="#"><i class="icon icon-box"></i> {{t 'hostsPage.hostPage.navTabs.containers'}}</a>{{/link-to}} {{#link-to "host.containers" tagName="li" href=false}}<a href="#"><i class="icon icon-box"></i> {{t 'hostsPage.hostPage.navTabs.containers'}}</a>{{/link-to}}
{{#link-to "host.ports" tagName="li" href=false}}<a href="#"><i class="icon icon-share"></i> {{t 'hostsPage.hostPage.navTabs.ports'}}</a>{{/link-to}} {{#link-to "host.ports" tagName="li" href=false}}<a href="#"><i class="icon icon-share"></i> {{t 'hostsPage.hostPage.navTabs.ports'}}</a>{{/link-to}}
@ -114,4 +133,7 @@
<div class="table-flat well"> <div class="table-flat well">
{{outlet}} {{outlet}}
</div> </div>
</div>
</div>
</div>
</section> </section>

View File

@ -1,35 +1,79 @@
import Ember from 'ember'; import Ember from 'ember';
import C from 'ui/utils/constants'; import C from 'ui/utils/constants';
const DROPDOWNCLOSETIMER = 250;
const SELECTOR = '.navbar .dropdown';
const WINDOW_SM = 694;
let timerObj = null;
let dropdown = null;
export default Ember.Mixin.create({ export default Ember.Mixin.create({
dropdownSelector: '.navbar .dropdown',
didInsertElement: function() { didInsertElement: function() {
const dropdownCloseTimer = 250; let $body = Ember.$('BODY');
let dropdown = null;
let timerObj = null;
let selector = this.get('dropdownSelector');
this.$().on('click', selector, (e) => { if ($body.hasClass('touch') && Ember.$(window).width() <= WINDOW_SM) {
let anchor = Ember.$(e.target).closest('A');
if ( anchor.hasClass('dropdown-toggle') && anchor[0].href.match(/#$/) ) {
e.preventDefault();
e.stopPropagation();
return false;
}
timerObj = null; // below iphone 6plus vertical width no need for dropdown logic
dropdown = null; this.$().on('click', SELECTOR, () => {
Ember.$('#navbar').collapse('toggle');
let collapsedNav = Ember.$('#navbar');
if (collapsedNav.hasClass('in')) {
collapsedNav.collapse('toggle');
}
this.clearHeaderMenus();
}); });
this.$().on('mouseenter', selector, (e) => { } else if ($body.hasClass('touch') && Ember.$(window).width() > WINDOW_SM) {
// ipad/tablet width
$body.on('touchend', (e) => {
let $el = $(e.target);
if ($el.closest('.navbar').length < 1) {
Ember.run.later(() => {
this.clearHeaderMenus();
});
}
});
this.$().on('touchstart', SELECTOR, (e) => {
let $el = $(e.currentTarget).find('a.dropdown-toggle');
if ($el.attr('aria-expanded') === 'false') {
e.preventDefault();
e.stopPropagation();
this.enterHandler(e);
}
});
this.$().on('click', SELECTOR, (e) => {
let $el = $(e.currentTarget).find('a.dropdown-toggle');
if ($el.attr('aria-expanded') === 'true') {
Ember.run.next(() => {
this.clearHeaderMenus();
});
}
});
} else {
// desktop width
this.$().on('click', SELECTOR, (e) => {
this.onClickHandler(e, false);
});
this.$().on('mouseenter', SELECTOR, (e) => {
this.enterHandler(e, false);
});
this.$().on('mouseleave', SELECTOR, () => {
this.leaveHandler();
});
this.$().on('keydown', `${SELECTOR} a`, (e) => {
this.keydownHandler(e);
});
}
},
enterHandler(e) {
let anchor = Ember.$(e.currentTarget).find('a:first'); let anchor = Ember.$(e.currentTarget).find('a:first');
Ember.run.cancel(timerObj); Ember.run.cancel(timerObj);
@ -42,7 +86,6 @@ export default Ember.Mixin.create({
this.clearHeaderMenus(); this.clearHeaderMenus();
dropdown = null;
dropdown = Ember.$(e.currentTarget).find('ul'); dropdown = Ember.$(e.currentTarget).find('ul');
if (dropdown) { if (dropdown) {
@ -58,23 +101,41 @@ export default Ember.Mixin.create({
} }
} }
}); },
this.$().on('mouseleave', selector, () => { leaveHandler() {
timerObj = Ember.run.later(() => { timerObj = Ember.run.later(() => {
if (dropdown) { if (dropdown) {
this.clearHeaderMenus(); this.clearHeaderMenus();
dropdown = null;
timerObj = null; timerObj = null;
} }
}, dropdownCloseTimer); }, DROPDOWNCLOSETIMER);
}); },
this.$().on('keydown', `${selector} a`, (e) => { onClickHandler(e) {
let anchor = Ember.$(e.target).closest('A');
if ( anchor.hasClass('dropdown-toggle') && anchor[0].href.match(/#$/) ) {
e.preventDefault();
e.stopPropagation();
return false;
}
timerObj = null;
let collapsedNav = Ember.$('#navbar');
if (collapsedNav.hasClass('in')) {
collapsedNav.collapse('toggle');
}
this.clearHeaderMenus();
},
keydownHandler(e) {
let items = this.get('items'); let items = this.get('items');
let currentIndex = 0; let currentIndex = 0;
@ -124,10 +185,13 @@ export default Ember.Mixin.create({
default: default:
break; break;
} }
});
}, },
showMenu: function(el, drpd) { showMenu: function(el, drpd) {
let body = Ember.$('BODY');
if (body.hasClass('touch')) {
Ember.$('BODY').addClass('nav-dropdown-open');
}
drpd.addClass('block'); drpd.addClass('block');
if (el.attr('aria-expanded')) { if (el.attr('aria-expanded')) {
el.attr('aria-expanded', true); el.attr('aria-expanded', true);
@ -135,8 +199,14 @@ 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.$('.navbar'); const navbar = Ember.$('.navbar');
dropdown = null;
navbar.find('.dropdown-menu.block').removeClass('block'); navbar.find('.dropdown-menu.block').removeClass('block');
navbar.find('a.dropdown-toggle[aria-expanded=true]').attr('aria-expanded', false); navbar.find('a.dropdown-toggle[aria-expanded=true]').attr('aria-expanded', false);
} }

View File

@ -79,7 +79,7 @@ $play-button-height-width: 60px;
&.machine { &.machine {
font-size: 12px; font-size: 12px;
height: 220px; height: 225px;
&.inactive-driver { &.inactive-driver {
background-color: $table-bg-accent; background-color: $table-bg-accent;

View File

@ -17,9 +17,16 @@
</section> </section>
<section> <section>
{{#info-multi-stats model=model.vm linkName="containerStats" single=true showGraphs=false}} <div class="row">
<div class="container-flex bordered"> <div class="col-md-3">
<div class="col-flex"> {{#if model.vm.description}}
<div>
<label>{{t 'virtualMachinePage.multistat.description'}} </label>
<span>{{model.vm.description}}</span>
</div>
<hr>
{{/if}}
<div>
<label>{{t 'virtualMachinePage.multistat.host'}}</label> <label>{{t 'virtualMachinePage.multistat.host'}}</label>
{{#if model.vm.primaryHost}} {{#if model.vm.primaryHost}}
{{#link-to "host" model.vm.primaryHost.id}}{{model.vm.primaryHost.displayName}}{{/link-to}} {{#link-to "host" model.vm.primaryHost.id}}{{model.vm.primaryHost.displayName}}{{/link-to}}
@ -27,43 +34,39 @@
<span class="text-muted">Unknown</span> <span class="text-muted">Unknown</span>
{{/if}} {{/if}}
</div> </div>
<div class="col-flex">
<hr>
<div>
<label>{{t 'virtualMachinePage.multistat.hostIp'}}</label> <label>{{t 'virtualMachinePage.multistat.hostIp'}}</label>
{{format-ip ip=model.vm.primaryHost.displayIp noIp='formatIp.unknownIp' showCopy=true}} {{format-ip ip=model.vm.primaryHost.displayIp noIp='formatIp.unknownIp' showCopy=true}}
</div> </div>
<div class="col-flex">
<hr>
<div>
<label>{{t 'virtualMachinePage.multistat.vmIp'}}</label> <label>{{t 'virtualMachinePage.multistat.vmIp'}}</label>
{{format-ip ip=model.vm.displayIp showCopy=true}} {{format-ip ip=model.vm.displayIp showCopy=true}}
</div> </div>
</div>
<div class="container-flex bordered"> <hr>
<div class="col-flex">
<div>
<label>{{t 'virtualMachinePage.multistat.image'}}</label> <label>{{t 'virtualMachinePage.multistat.image'}}</label>
<span class="force-wrap"> <span class="force-wrap">
{{model.vm.displayImage}} {{copy-to-clipboard clipboardText=model.vm.displayImage size="small"}} {{model.vm.displayImage}} {{copy-to-clipboard clipboardText=model.vm.displayImage size="small"}}
</span> </span>
</div> </div>
<div class="col-flex">
</div> </div>
<div class="col-flex"> <div class="col-md-9">
</div> <div class="row r-p15">
</div>
{{#if model.vm.description}}
<div class="container-flex bordered">
<div class="col-flex">
<label>{{t 'virtualMachinePage.multistat.description'}} </label>
<span>{{model.vm.description}}</span>
</div>
</div>
{{/if}}
{{/info-multi-stats}}
</section>
<section>
<ul class="nav nav-tabs nav-tabs-well shadowed"> <ul class="nav nav-tabs nav-tabs-well shadowed">
{{#link-to "virtualmachine.labels" tagName="li" href=false}}<a href="#"><i class="icon icon-tag"></i> {{t 'virtualMachinePage.navTabs.labels'}}</a>{{/link-to}} {{#link-to "virtualmachine.labels" tagName="li" href=false}}<a href="#"><i class="icon icon-tag"></i> {{t 'virtualMachinePage.navTabs.labels'}}</a>{{/link-to}}
</ul> </ul>
<div class="table-flat well"> <div class="table-flat well">
{{outlet}} {{outlet}}
</div> </div>
</div>
</div>
</div>
</section> </section>