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) {
var tgt = Ember.$(e.target);
var more = tgt.closest('.more-actions');
var offsets = {
y: -1,
x: 2,
mirror: true
};
if ( more && more.length ) {
e.preventDefault();
e.stopPropagation();
@ -31,7 +36,7 @@ export default Ember.Component.extend({
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 NewOrEdit from 'ui/mixins/new-or-edit';
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(),
settings : Ember.inject.service(),
prefs: Ember.inject.service(),
service : null,
editing : null,
@ -252,6 +254,7 @@ export default Ember.Component.extend(NewOrEdit, {
},
doneSaving() {
this._super(...arguments);
this.send('done');
},
});

View File

@ -1,6 +1,7 @@
import Ember from 'ember';
import NewOrEdit from 'ui/mixins/new-or-edit';
import Errors from 'ui/utils/errors';
import StackState from 'ui/mixins/stack-memory';
const HOSTNAME = 'externalhostname';
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(),
record: null,
@ -156,6 +157,7 @@ export default Ember.Component.extend(NewOrEdit, {
},
doneSaving() {
this.send('cancel');
this._super(...arguments);
this.send('done');
},
});

View File

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

View File

@ -94,7 +94,7 @@
<ul class="nav-user dropdown">
<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}}
{{identity-avatar link=false identity=access.identity}}
{{else}}

View File

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

View File

@ -10,6 +10,13 @@ let timerObj = null;
let dropdown = null;
export default Ember.Mixin.create({
willRender(){
if (Ember.$('BODY').hasClass('touch')) {
Ember.run.later(() => {
this.clearHeaderMenus();
}, DROPDOWNCLOSETIMER);
}
},
didInsertElement: function() {
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.stopPropagation();
this.touchHandler(e);
@ -111,7 +118,20 @@ export default Ember.Mixin.create({
},
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);
@ -126,15 +146,15 @@ export default Ember.Mixin.create({
dropdown = Ember.$(e.currentTarget).find('ul');
if (dropdown) {
this.showMenu(anchor, dropdown);
this.showMenu(anchor, dropdown, offset);
}
}
} else { // no dropdown open
dropdown = Ember.$(e.currentTarget).find('ul');
dropdown = Ember.$(e.currentTarget).find(findTarget);
if (dropdown) {
this.showMenu(anchor, dropdown);
this.showMenu(anchor, dropdown, offset);
}
}
@ -224,15 +244,10 @@ export default Ember.Mixin.create({
}
},
showMenu: function(el, drpd) {
let body = Ember.$('BODY');
if (body.hasClass('touch')) {
Ember.$('BODY').addClass('nav-dropdown-open');
}
showMenu: function(el, drpd, offset) {
drpd.addClass('invisible');
drpd.addClass('block');
positionDropdown(drpd, el, drpd.hasClass('dropdown-menu-right'));
positionDropdown(drpd, el, drpd.hasClass('dropdown-menu-right'), offset);
drpd.removeClass('invisible');
if (el.attr('aria-expanded')) {
@ -241,10 +256,6 @@ export default Ember.Mixin.create({
},
clearHeaderMenus: function() {
let body = Ember.$('BODY');
if (body.hasClass('touch')) {
Ember.$('BODY').removeClass('nav-dropdown-open');
}
const navbar = Ember.$(PARENT);
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({
application: Ember.inject.controller(),
projects: Ember.inject.service(),
service: Ember.computed.alias('model.service'),
rules: Ember.computed.alias('service.lbConfig.portRules'),

View File

@ -17,6 +17,9 @@ export default Ember.Route.extend({
if (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) {

View File

@ -22,6 +22,28 @@
<section>
<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}}
<div class="inline-block">
<label class="acc-label p-0">{{t 'containersPage.containerPage.infoMultiStats.memoryReservation.labelText'}}</label>
@ -57,27 +79,14 @@
</div>
{{/if}}
{{#if service.hasImage}}
{{#if model.stack}}
<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"}}
<label class="acc-label p-0">{{t 'generic.stack'}}:</label>
{{#link-to "stack" projects.current.id model.stack.id}}{{model.stack.displayName}}{{/link-to}}
</div>
{{/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>
</section>

View File

@ -8,7 +8,16 @@ export default Ember.Service.extend({
actionToggle : 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')) {
this.hide();
}
@ -52,7 +61,7 @@ export default Ember.Service.extend({
this.set('open',true);
// Delay ensure it works in firefox
Ember.run.next(() => {
positionDropdown($menu, trigger, true);
positionDropdown($menu, trigger, true, offset);
$('#resource-actions-first')[0].focus();
$menu.css('visibility','visible');
});

View File

@ -1,5 +1,6 @@
import Ember from 'ember';
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({
prefs: Ember.inject.service(),
@ -99,11 +100,68 @@ export default Ember.Controller.extend({
name: 'scale',
sort: ['scale:desc','isGlobalScale:desc','displayName'],
searchField: null,
width: 100,
translationKey: 'stacksPage.table.scale',
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) {
return this.get('model.services').filter((service) => {

View File

@ -1,10 +1,27 @@
import Ember from 'ember';
export default Ember.Route.extend({
parentRoute: 'stack',
resetController: function (controller, isExisting/*, transition*/) {
if (isExisting)
{
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}}
{{#if model.stack.description}}
{{banner-message color='bg-secondary mb-0 mt-10' message=model.stack.description}}
{{/if}}
<section>
{{#accordion-list as |al expandFn| }}
{{#accordion-list-item
title=(t 'stackPage.containers.header')
detail=(t 'stackPage.containers.detail')
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
expand=(action expandFn)
componentName='container-table'
as | parent |
}}
{{component parent.intent
body=instances
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
{{#sortable-table
tableClassNames="double-rows"
classNames="grid sortable-table"
fullRows=true
headers=sgHeaders
pagingLabel="pagination.service"
body=rows
searchText=searchText
sortBy=sortBy
stickyHeader=false
subHeaders=containerHeaders
bulkActions=true
subRows=true
fullRows=true
pagingLabel="pagination.containerService"
subSearchField="instances"
as |sortable kind serv dt|}}
extraSearchFields=extraSearchFields
extraSearchSubFields=extraSearchSubFields
headers=sgHeaders as |sortable kind inst dt|}}
{{#if (eq kind "row")}}
{{service-row
canExpand=true
expanded=(array-includes expandedInstances serv.id)
fullColspan=sortable.fullColspan
model=serv
searchText=searchText
showInstanceCount=false
subMatches=sortable.subMatches
toggle=(action "toggleExpand" serv.id)
dt=dt
}}
{{#if (eq inst.baseType "instance")}}
{{container-row
model=inst
dt=dt
showHost=true
expandPlaceholder=true
scalePlaceholder=true
fullColspan=sortable.fullColspan
}}
{{else}}
{{service-row
model=inst
toggle=(action "toggleExpand" inst.id)
expanded=(array-includes expandedInstances inst.id)
searchText=searchText
subMatches=sortable.subMatches
fullColspan=sortable.fullColspan
dt=dt
}}
{{/if}}
{{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>
<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}}
{{/component}}
{{/sortable-table}}
{{/accordion-list-item}}
{{#accordion-list-item
@ -144,5 +140,48 @@
{{/if}}
{{/component}}
{{/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}}
</section>

View File

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

View File

@ -171,7 +171,6 @@
//nav
.responsive-nav {
position: relative;
overflow: hidden;
background-color: $header;
}
@ -202,7 +201,9 @@
}
/* Class added via JS when toggled open */
.nav-open {
overflow: visible;
.nav-list {
max-height: 1000px;
}
}
/* NAV LIST*/
@ -213,6 +214,13 @@
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
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%;
margin-left: 10px;
}
.page-header NAV .dropdown-menu {
.page-header NAV .nav-list .dropdown-menu {
background: transparent;
box-shadow: none;
max-width: 100%;

View File

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

View File

@ -6,7 +6,7 @@ export function resizeDropdown(event) {
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
var direction = (right === true ? 'right' : 'left');
var $menu = $(menu);
@ -17,19 +17,20 @@ export function positionDropdown(menu, trigger, right) {
left: 0
});
// calculate new position
var calculator = new $.PositionCalculator({
let pco = {
item: $menu,
target: trigger,
itemAt: 'top ' + direction,
itemOffset: {
y: 3,
x: 0,
mirror: true
},
targetAt: 'bottom ' + direction,
flip: 'both'
});
};
if (offset) {
pco.itemOffset = offset;
}
// calculate new position
var calculator = new $.PositionCalculator(pco);
var posResult = calculator.calculate();
// set new position

View File

@ -7,17 +7,19 @@ export default Ember.Route.extend({
},
},
model: function(params) {
let out = Ember.Object.create({
volume: this.get('store').getById(params.type, params.volume_id)
});
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) {
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;
},
});

View File

@ -922,7 +922,7 @@ stackPage:
backLink: Back to all stacks
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: |
{count, plural,
=0 {No containers}
@ -956,6 +956,15 @@ stackPage:
=1 {# container}
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:
header: Import Compose.yml