mirror of
https://github.com/status-im/consul.git
synced 2025-02-18 00:27:04 +00:00
ui: Centralized Config Intention Permission CRUD (#8762)
This commit is contained in:
parent
5261bb0d0d
commit
ed9826bbbe
@ -1,4 +1,5 @@
|
|||||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||||
|
import { get } from '@ember/object';
|
||||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||||
// Intentions use SourceNS and DestinationNS properties for namespacing
|
// Intentions use SourceNS and DestinationNS properties for namespacing
|
||||||
// so we don't need to add the `?ns=` anywhere here
|
// so we don't need to add the `?ns=` anywhere here
|
||||||
@ -25,55 +26,66 @@ export default Adapter.extend({
|
|||||||
if (typeof id === 'undefined') {
|
if (typeof id === 'undefined') {
|
||||||
throw new Error('You must specify an id');
|
throw new Error('You must specify an id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the information we need from the id, which has been previously encoded
|
||||||
const [SourceNS, SourceName, DestinationNS, DestinationName] = id
|
const [SourceNS, SourceName, DestinationNS, DestinationName] = id
|
||||||
.split(':')
|
.split(':')
|
||||||
.map(decodeURIComponent);
|
.map(decodeURIComponent);
|
||||||
|
|
||||||
return request`
|
return request`
|
||||||
GET /v1/connect/intentions/exact?source=${SourceNS +
|
GET /v1/connect/intentions/exact?${{
|
||||||
'/' +
|
source: `${SourceNS}/${SourceName}`,
|
||||||
SourceName}&destination=${DestinationNS + '/' + DestinationName}&${{ dc }}
|
destination: `${DestinationNS}/${DestinationName}`,
|
||||||
|
dc: dc,
|
||||||
|
}}
|
||||||
Cache-Control: no-store
|
Cache-Control: no-store
|
||||||
|
|
||||||
${{ index }}
|
${{ index }}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
requestForCreateRecord: function(request, serialized, data) {
|
requestForCreateRecord: function(request, serialized, data) {
|
||||||
// TODO: need to make sure we remove dc
|
const body = {
|
||||||
return request`
|
SourceNS: serialized.SourceNS,
|
||||||
POST /v1/connect/intentions?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
DestinationNS: serialized.DestinationNS,
|
||||||
|
SourceName: serialized.SourceName,
|
||||||
|
DestinationName: serialized.DestinationName,
|
||||||
|
SourceType: serialized.SourceType,
|
||||||
|
Meta: serialized.Meta,
|
||||||
|
Description: serialized.Description,
|
||||||
|
};
|
||||||
|
|
||||||
${{
|
// only send the Action if we have one
|
||||||
SourceNS: serialized.SourceNS,
|
if (get(serialized, 'Action.length')) {
|
||||||
DestinationNS: serialized.DestinationNS,
|
body.Action = serialized.Action;
|
||||||
SourceName: serialized.SourceName,
|
} else {
|
||||||
DestinationName: serialized.DestinationName,
|
// otherwise only send Permissions if we have them
|
||||||
SourceType: serialized.SourceType,
|
if (serialized.Permissions) {
|
||||||
Action: serialized.Action,
|
body.Permissions = serialized.Permissions;
|
||||||
Description: serialized.Description,
|
}
|
||||||
|
}
|
||||||
|
return request`
|
||||||
|
PUT /v1/connect/intentions/exact?${{
|
||||||
|
source: `${data.SourceNS}/${data.SourceName}`,
|
||||||
|
destination: `${data.DestinationNS}/${data.DestinationName}`,
|
||||||
|
[API_DATACENTER_KEY]: data[DATACENTER_KEY],
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
${body}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
requestForUpdateRecord: function(request, serialized, data) {
|
requestForUpdateRecord: function(request, serialized, data) {
|
||||||
return request`
|
// you can no longer save Destinations
|
||||||
PUT /v1/connect/intentions/${data.LegacyID}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
delete serialized.DestinationNS;
|
||||||
|
delete serialized.DestinationName;
|
||||||
${{
|
return this.requestForCreateRecord(...arguments);
|
||||||
SourceNS: serialized.SourceNS,
|
|
||||||
DestinationNS: serialized.DestinationNS,
|
|
||||||
SourceName: serialized.SourceName,
|
|
||||||
DestinationName: serialized.DestinationName,
|
|
||||||
SourceType: serialized.SourceType,
|
|
||||||
Action: serialized.Action,
|
|
||||||
Meta: serialized.Meta,
|
|
||||||
Description: serialized.Description,
|
|
||||||
}}
|
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
requestForDeleteRecord: function(request, serialized, data) {
|
requestForDeleteRecord: function(request, serialized, data) {
|
||||||
return request`
|
return request`
|
||||||
DELETE /v1/connect/intentions/${data.LegacyID}?${{
|
DELETE /v1/connect/intentions/exact?${{
|
||||||
[API_DATACENTER_KEY]: data[DATACENTER_KEY],
|
source: `${data.SourceNS}/${data.SourceName}`,
|
||||||
}}
|
destination: `${data.DestinationNS}/${data.DestinationName}`,
|
||||||
|
[API_DATACENTER_KEY]: data[DATACENTER_KEY],
|
||||||
|
}}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -92,7 +92,9 @@
|
|||||||
{{nspace.Name}}
|
{{nspace.Name}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</PowerSelectWithCreate>
|
</PowerSelectWithCreate>
|
||||||
|
{{#if create}}
|
||||||
<em>For the destination, you may choose any namespace for which you have access.</em>
|
<em>For the destination, you may choose any namespace for which you have access.</em>
|
||||||
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -112,12 +114,17 @@
|
|||||||
header="Deny"
|
header="Deny"
|
||||||
body="The source service will not be allowed to connect to the destination."
|
body="The source service will not be allowed to connect to the destination."
|
||||||
)
|
)
|
||||||
|
(hash
|
||||||
|
intent=""
|
||||||
|
header="L7 Permissions"
|
||||||
|
body="The source service may or may not connect to the destination service via unique permissions based on L7 criteria: path, header, or method."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
as |_action|}}
|
as |_action|}}
|
||||||
<RadioCard
|
<RadioCard
|
||||||
class={{concat 'value-' _action.intent}}
|
class={{concat 'value-' _action.intent}}
|
||||||
@value={{_action.intent}}
|
@value={{_action.intent}}
|
||||||
@checked={{if (eq item.Action _action.intent) 'checked'}}
|
@checked={{if (eq (or item.Action '') _action.intent) 'checked'}}
|
||||||
@onchange={{action onchange}}
|
@onchange={{action onchange}}
|
||||||
@name="Action"
|
@name="Action"
|
||||||
as |radio|>
|
as |radio|>
|
||||||
@ -131,13 +138,10 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="type-text{{if item.error.Description ' has-error'}}">
|
|
||||||
<span>Description (Optional)</span>
|
|
||||||
<input type="text" name="Description" value={{item.Description}} placeholder="Description (Optional)" onchange={{action onchange}} />
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{#if (not item.Legacy)}}
|
{{#if (eq (or item.Action '') '')}}
|
||||||
<fieldset>
|
<fieldset class="permissions">
|
||||||
|
<button type="button" onclick={{action (mut shouldShowPermissionForm) true}}>Add permission</button>
|
||||||
<h2>Permissions</h2>
|
<h2>Permissions</h2>
|
||||||
{{#if (gt item.Permissions.length 0) }}
|
{{#if (gt item.Permissions.length 0) }}
|
||||||
<div class="notice info">
|
<div class="notice info">
|
||||||
@ -148,17 +152,21 @@
|
|||||||
<a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>
|
<a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl-migrate-tokens.html" target="_blank" rel="noopener noreferrer">documentation</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ConsulIntentionPermissionList @items={{item.Permissions}} />
|
<ConsulIntentionPermissionList
|
||||||
|
@items={{item.Permissions}}
|
||||||
|
@onclick={{queue (action (mut permission)) (action (mut shouldShowPermissionForm) true)}}
|
||||||
|
@ondelete={{action 'delete' 'Permissions' item}}
|
||||||
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
<BlockSlot @name="header">
|
<BlockSlot @name="header">
|
||||||
<h3>
|
<h3>
|
||||||
Add permissions via CLI
|
No permissions yet
|
||||||
</h3>
|
</h3>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="body">
|
<BlockSlot @name="body">
|
||||||
<p>
|
<p>
|
||||||
Permissions intercept an Intention's traffic using L7 criteria, such as path prefixes and http headers. You can use the CLI to add permissions to this intention.
|
Permissions intercept an Intention's traffic using L7 criteria, such as path prefixes and http headers.
|
||||||
</p>
|
</p>
|
||||||
</BlockSlot>
|
</BlockSlot>
|
||||||
<BlockSlot @name="actions">
|
<BlockSlot @name="actions">
|
||||||
@ -173,4 +181,47 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<fieldset>
|
||||||
|
<label class="type-text{{if item.error.Description ' has-error'}}">
|
||||||
|
<span>Description (Optional)</span>
|
||||||
|
<input type="text" name="Description" value={{item.Description}} placeholder="Description (Optional)" onchange={{action onchange}} />
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{{#if shouldShowPermissionForm}}
|
||||||
|
<ModalDialog
|
||||||
|
class="consul-intention-permission-modal"
|
||||||
|
@onclose={{queue (action (mut shouldShowPermissionForm) false) (action (mut permission) undefined)}}
|
||||||
|
as |modal|>
|
||||||
|
<BlockSlot @name="header">
|
||||||
|
<h3>Edit Permission</h3>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="body">
|
||||||
|
<ConsulIntentionPermissionForm
|
||||||
|
@item={{permission}}
|
||||||
|
@onsubmit={{action 'add' 'Permissions' item}}
|
||||||
|
as |permissionForm|>
|
||||||
|
<Ref @target={{this}} @name="permissionForm" @value={{permissionForm}} />
|
||||||
|
</ConsulIntentionPermissionForm>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="type-submit"
|
||||||
|
disabled={{if (not this.permissionForm.isDirty) 'disabled'}}
|
||||||
|
onclick={{queue (action this.permissionForm.submit) (action modal.close)}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="type-cancel"
|
||||||
|
onclick={{queue (action this.permissionForm.reset) (action modal.close)}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</BlockSlot>
|
||||||
|
</ModalDialog>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -2,6 +2,9 @@ import Component from '@ember/component';
|
|||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
tagName: '',
|
tagName: '',
|
||||||
|
|
||||||
|
shouldShowPermissionForm: false,
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
createNewLabel: function(template, term) {
|
createNewLabel: function(template, term) {
|
||||||
return template.replace(/{{term}}/g, term);
|
return template.replace(/{{term}}/g, term);
|
||||||
@ -9,5 +12,17 @@ export default Component.extend({
|
|||||||
isUnique: function(items, term) {
|
isUnique: function(items, term) {
|
||||||
return !items.findBy('Name', term);
|
return !items.findBy('Name', term);
|
||||||
},
|
},
|
||||||
|
add: function(name, changeset, value) {
|
||||||
|
if (!(changeset.get(name) || []).includes(value) && value.isNew) {
|
||||||
|
changeset.pushObject(name, value);
|
||||||
|
changeset.validate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete: function(name, changeset, value) {
|
||||||
|
if ((changeset.get(name) || []).includes(value)) {
|
||||||
|
changeset.removeObject(name, value);
|
||||||
|
changeset.validate();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
.consul-intention-fieldsets {
|
||||||
|
[role='radiogroup'] {
|
||||||
|
overflow: visible !important;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 12px;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(270px, auto));
|
||||||
|
}
|
||||||
|
.radio-card {
|
||||||
|
@extend %radio-card-with-icon;
|
||||||
|
}
|
||||||
|
.radio-card header > * {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.value-allow > :last-child::before {
|
||||||
|
@extend %with-arrow-right-color-icon, %as-pseudo;
|
||||||
|
}
|
||||||
|
.value-deny > :last-child::before {
|
||||||
|
@extend %with-deny-color-icon, %as-pseudo;
|
||||||
|
}
|
||||||
|
.permissions > button {
|
||||||
|
@extend %anchor;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.consul-intention-permission-modal [role="dialog"] > div > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
19
ui-v2/app/components/consul-intention-list/index.scss
Normal file
19
ui-v2/app/components/consul-intention-list/index.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@import './skin';
|
||||||
|
@import './layout';
|
||||||
|
.consul-intention-list td.source,
|
||||||
|
.consul-intention-list td.destination {
|
||||||
|
@extend %tbody-th;
|
||||||
|
}
|
||||||
|
.consul-intention-list td strong {
|
||||||
|
@extend %pill-700;
|
||||||
|
}
|
||||||
|
.consul-intention-list td.intent-allow strong {
|
||||||
|
@extend %pill-allow;
|
||||||
|
}
|
||||||
|
.consul-intention-list td.intent-deny strong {
|
||||||
|
@extend %pill-deny;
|
||||||
|
}
|
||||||
|
.consul-intention-list td.intent-l7-rules strong {
|
||||||
|
@extend %pill-l7;
|
||||||
|
}
|
||||||
|
|
29
ui-v2/app/components/consul-intention-list/layout.scss
Normal file
29
ui-v2/app/components/consul-intention-list/layout.scss
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.consul-intention-list {
|
||||||
|
td {
|
||||||
|
height: 59px;
|
||||||
|
}
|
||||||
|
tr > *:nth-child(1) {
|
||||||
|
width: calc(30% - 50px);
|
||||||
|
}
|
||||||
|
tr > *:nth-child(2) {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
tr > *:nth-child(3) {
|
||||||
|
width: calc(30% - 50px);
|
||||||
|
}
|
||||||
|
tr > *:nth-child(4) {
|
||||||
|
width: calc(40% - 220px);
|
||||||
|
}
|
||||||
|
tr > *:nth-child(5) {
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
tr > *:last-child {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{$--lt-horizontal-nav} {
|
||||||
|
.consul-intention-list tr > :not(.source):not(.destination):not(.intent) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
5
ui-v2/app/components/consul-intention-list/skin.scss
Normal file
5
ui-v2/app/components/consul-intention-list/skin.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.consul-intention-list {
|
||||||
|
td.permissions {
|
||||||
|
color: $blue-500;
|
||||||
|
}
|
||||||
|
}
|
135
ui-v2/app/components/consul-intention-permission-form/index.hbs
Normal file
135
ui-v2/app/components/consul-intention-permission-form/index.hbs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<div
|
||||||
|
...attributes
|
||||||
|
class="consul-intention-permission-form"
|
||||||
|
>
|
||||||
|
{{yield (hash
|
||||||
|
submit=(action 'submit' changeset)
|
||||||
|
reset=(action 'reset' changeset)
|
||||||
|
|
||||||
|
isDirty=(and changeset.isValid)
|
||||||
|
changeset=changeset
|
||||||
|
)}}
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<span class="label">
|
||||||
|
Should this permission allow the source connect to the destination?
|
||||||
|
</span>
|
||||||
|
<div role="radiogroup" class={{if changeset.error.Action ' has-error'}}>
|
||||||
|
{{#each intents as |intent|}}
|
||||||
|
<label>
|
||||||
|
<span>{{capitalize intent}}</span>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="Action"
|
||||||
|
value={{intent}}
|
||||||
|
checked={{if (eq changeset.Action intent) 'checked'}}
|
||||||
|
onchange={{action (changeset-set changeset 'Action') value="target.value"}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<header>
|
||||||
|
<h2>Path</h2>
|
||||||
|
</header>
|
||||||
|
<div>
|
||||||
|
<label class="type-select">
|
||||||
|
<span>Path Type</span>
|
||||||
|
<PowerSelect
|
||||||
|
@options={{pathTypes}}
|
||||||
|
@selected={{pathType}}
|
||||||
|
@onChange={{action 'change' 'HTTP.PathType' changeset}} as |Type|>
|
||||||
|
{{get pathLabels Type}}
|
||||||
|
</PowerSelect>
|
||||||
|
</label>
|
||||||
|
{{#if shouldShowPathField}}
|
||||||
|
<label class="type-text{{if changeset.error.HTTP.Path ' has-error'}}">
|
||||||
|
<span>{{get pathLabels pathType}}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Path"
|
||||||
|
value={{changeset-get changeset 'HTTP.Path'}}
|
||||||
|
oninput={{action 'change' 'HTTP.Path' changeset}}
|
||||||
|
/>
|
||||||
|
{{#if changeset.error.HTTP.Path}}
|
||||||
|
<strong>
|
||||||
|
{{#if (eq (changeset-get changeset 'HTTP.PathType') 'PathRegex')}}
|
||||||
|
Path Regex should not be blank
|
||||||
|
{{else}}
|
||||||
|
Path should begin with a '/'
|
||||||
|
{{/if}}
|
||||||
|
</strong>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<h2>Methods</h2>
|
||||||
|
<div class="type-toggle">
|
||||||
|
<span>All methods are applied by default unless specified</span>
|
||||||
|
<label class="type-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="{{name}}[allMethods]"
|
||||||
|
checked={{if allMethods 'checked'}}
|
||||||
|
onchange={{action 'change' 'allMethods' changeset}}
|
||||||
|
/>
|
||||||
|
<span>All methods</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if shouldShowMethods}}
|
||||||
|
<div class="checkbox-group" role="group">
|
||||||
|
{{#each methods as |method|}}
|
||||||
|
<label class="type-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="method"
|
||||||
|
value={{method}}
|
||||||
|
checked={{if (contains method changeset.HTTP.Methods) 'checked'}}
|
||||||
|
onchange={{action 'change' 'method' changeset}}
|
||||||
|
/>
|
||||||
|
<span>{{method}}</span>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<h2>Headers</h2>
|
||||||
|
<ConsulIntentionPermissionHeaderList
|
||||||
|
@items={{changeset-get changeset 'HTTP.Header'}}
|
||||||
|
@ondelete={{action 'delete' 'HTTP.Header' changeset}}
|
||||||
|
as |headerList|>
|
||||||
|
|
||||||
|
</ConsulIntentionPermissionHeaderList>
|
||||||
|
|
||||||
|
<ConsulIntentionPermissionHeaderForm
|
||||||
|
@onsubmit={{action 'add' 'HTTP.Header' changeset}}
|
||||||
|
as |headerForm|>
|
||||||
|
<Ref @target={{this}} @name="headerForm" @value={{headerForm}} />
|
||||||
|
</ConsulIntentionPermissionHeaderForm>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="type-submit"
|
||||||
|
disabled={{if (not this.headerForm.isDirty) 'disabled'}}
|
||||||
|
onclick={{action this.headerForm.submit}}
|
||||||
|
>
|
||||||
|
Add another header
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="type-cancel"
|
||||||
|
onclick={{action this.headerForm.reset}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
123
ui-v2/app/components/consul-intention-permission-form/index.js
Normal file
123
ui-v2/app/components/consul-intention-permission-form/index.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
import { get, set, computed } from '@ember/object';
|
||||||
|
import { alias, not, equal } from '@ember/object/computed';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
const name = 'intention-permission';
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
name: name,
|
||||||
|
|
||||||
|
schema: service('schema'),
|
||||||
|
change: service('change'),
|
||||||
|
repo: service(`repository/${name}`),
|
||||||
|
|
||||||
|
onsubmit: function() {},
|
||||||
|
onreset: function() {},
|
||||||
|
|
||||||
|
intents: alias(`schema.${name}.Action.allowedValues`),
|
||||||
|
methods: alias(`schema.${name}-http.Methods.allowedValues`),
|
||||||
|
pathProps: alias(`schema.${name}-http.PathType.allowedValues`),
|
||||||
|
|
||||||
|
pathTypes: computed('pathProps', function() {
|
||||||
|
return ['NoPath'].concat(this.pathProps);
|
||||||
|
}),
|
||||||
|
|
||||||
|
pathLabels: computed(function() {
|
||||||
|
return {
|
||||||
|
NoPath: 'No Path',
|
||||||
|
PathExact: 'Exact',
|
||||||
|
PathPrefix: 'Prefixed by',
|
||||||
|
PathRegex: 'Regular Expression',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
pathInputLabels: computed(function() {
|
||||||
|
return {
|
||||||
|
PathExact: 'Exact Path',
|
||||||
|
PathPrefix: 'Path Prefix',
|
||||||
|
PathRegex: 'Path Regular Expression',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
changeset: computed('item', function() {
|
||||||
|
const changeset = this.change.changesetFor(name, this.item || this.repo.create());
|
||||||
|
if (changeset.isNew) {
|
||||||
|
changeset.validate();
|
||||||
|
}
|
||||||
|
return changeset;
|
||||||
|
}),
|
||||||
|
|
||||||
|
pathType: computed('changeset._changes.HTTP.PathType', 'pathTypes.firstObject', function() {
|
||||||
|
return this.changeset.HTTP.PathType || this.pathTypes.firstObject;
|
||||||
|
}),
|
||||||
|
noPathType: equal('pathType', 'NoPath'),
|
||||||
|
shouldShowPathField: not('noPathType'),
|
||||||
|
|
||||||
|
allMethods: false,
|
||||||
|
shouldShowMethods: not('allMethods'),
|
||||||
|
|
||||||
|
didReceiveAttrs: function() {
|
||||||
|
if (!get(this, 'item.HTTP.Methods.length')) {
|
||||||
|
set(this, 'allMethods', true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
change: function(name, changeset, e) {
|
||||||
|
const value = typeof get(e, 'target.value') !== 'undefined' ? e.target.value : e;
|
||||||
|
switch (name) {
|
||||||
|
case 'allMethods':
|
||||||
|
set(this, name, e.target.checked);
|
||||||
|
break;
|
||||||
|
case 'method':
|
||||||
|
if (e.target.checked) {
|
||||||
|
this.actions.add.apply(this, ['HTTP.Methods', changeset, value]);
|
||||||
|
} else {
|
||||||
|
this.actions.delete.apply(this, ['HTTP.Methods', changeset, value]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
changeset.set(name, value);
|
||||||
|
}
|
||||||
|
changeset.validate();
|
||||||
|
},
|
||||||
|
add: function(prop, changeset, value) {
|
||||||
|
changeset.pushObject(prop, value);
|
||||||
|
changeset.validate();
|
||||||
|
},
|
||||||
|
delete: function(prop, changeset, value) {
|
||||||
|
changeset.removeObject(prop, value);
|
||||||
|
changeset.validate();
|
||||||
|
},
|
||||||
|
submit: function(changeset, e) {
|
||||||
|
const pathChanged =
|
||||||
|
typeof changeset.changes.find(
|
||||||
|
({ key, value }) => key === 'HTTP.PathType' || key === 'HTTP.Path'
|
||||||
|
) !== 'undefined';
|
||||||
|
if (pathChanged) {
|
||||||
|
this.pathProps.forEach(prop => {
|
||||||
|
changeset.set(`HTTP.${prop}`, undefined);
|
||||||
|
});
|
||||||
|
if (changeset.HTTP.PathType !== 'NoPath') {
|
||||||
|
changeset.set(`HTTP.${changeset.HTTP.PathType}`, changeset.HTTP.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.allMethods) {
|
||||||
|
changeset.set('HTTP.Methods', null);
|
||||||
|
}
|
||||||
|
// this will prevent the changeset from overwriting the
|
||||||
|
// computed properties on the ED object
|
||||||
|
delete changeset._changes.HTTP.PathType;
|
||||||
|
delete changeset._changes.HTTP.Path;
|
||||||
|
//
|
||||||
|
this.repo.persist(changeset);
|
||||||
|
this.onsubmit(changeset.data);
|
||||||
|
},
|
||||||
|
reset: function(changeset, e) {
|
||||||
|
changeset.rollback();
|
||||||
|
this.onreset(changeset.data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,2 @@
|
|||||||
|
@import './skin';
|
||||||
|
@import './layout';
|
@ -0,0 +1,22 @@
|
|||||||
|
.consul-intention-permission-form {
|
||||||
|
h2 {
|
||||||
|
padding-top: 1.4em;
|
||||||
|
margin-top: .2em;
|
||||||
|
margin-bottom: .6em;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-header-form {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
fieldset:nth-child(2) > div,
|
||||||
|
.consul-intention-permission-header-form fieldset > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
grid-gap: 12px;
|
||||||
|
}
|
||||||
|
fieldset:nth-child(2) > div label:last-child {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
.ember-basic-dropdown-trigger {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
.consul-intention-permission-form {
|
||||||
|
h2 {
|
||||||
|
border-top: 1px solid $blue-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
|||||||
|
<div
|
||||||
|
...attributes
|
||||||
|
class="consul-intention-permission-header-form"
|
||||||
|
>
|
||||||
|
{{yield (hash
|
||||||
|
submit=(action 'submit' changeset)
|
||||||
|
reset=(action 'reset' changeset)
|
||||||
|
|
||||||
|
isDirty=(and changeset.isValid changeset.isDirty)
|
||||||
|
changeset=changeset
|
||||||
|
)}}
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<label class="type-select">
|
||||||
|
<span>Header Type</span>
|
||||||
|
<div>
|
||||||
|
<PowerSelect
|
||||||
|
@options={{headerTypes}}
|
||||||
|
@selected={{headerType}}
|
||||||
|
@onChange={{action 'change' 'HeaderType' changeset}} as |Type|>
|
||||||
|
{{get headerLabels Type}}
|
||||||
|
</PowerSelect>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="type-text{{if changeset.error.Name ' has-error'}}">
|
||||||
|
<span>Header name</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name={{concat name '[Name]'}}
|
||||||
|
value={{changeset-get changeset 'Name'}}
|
||||||
|
oninput={{action 'change' 'Name' changeset}}
|
||||||
|
/>
|
||||||
|
{{#if changeset.error.Name}}
|
||||||
|
<strong>{{changeset.error.Name.validation}}</strong>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{#if shouldShowValueField}}
|
||||||
|
<label class="type-text{{if changeset.error.Value ' has-error'}}">
|
||||||
|
<span>Header {{lowercase (get headerLabels headerType)}}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="Value"
|
||||||
|
value={{changeset-get changeset 'Value'}}
|
||||||
|
oninput={{action 'change' 'Value' changeset}}
|
||||||
|
/>
|
||||||
|
{{#if changeset.error.Value}}
|
||||||
|
<strong>{{changeset.error.Value.validation}}</strong>
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
@ -0,0 +1,85 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
import { get, set, computed } from '@ember/object';
|
||||||
|
import { alias, equal, not } from '@ember/object/computed';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
const name = 'intention-permission-http-header';
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
name: name,
|
||||||
|
|
||||||
|
schema: service('schema'),
|
||||||
|
change: service('change'),
|
||||||
|
repo: service(`repository/${name}`),
|
||||||
|
|
||||||
|
onsubmit: function() {},
|
||||||
|
onreset: function() {},
|
||||||
|
|
||||||
|
changeset: computed('item', function() {
|
||||||
|
return this.change.changesetFor(
|
||||||
|
name,
|
||||||
|
this.item ||
|
||||||
|
this.repo.create({
|
||||||
|
HeaderType: this.headerTypes.firstObject,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
headerTypes: alias(`schema.${name}.HeaderType.allowedValues`),
|
||||||
|
|
||||||
|
headerLabels: computed(function() {
|
||||||
|
return {
|
||||||
|
Exact: 'Exactly Matching',
|
||||||
|
Prefix: 'Prefixed by',
|
||||||
|
Suffix: 'Suffixed by',
|
||||||
|
Regex: 'Regular Expression',
|
||||||
|
Present: 'Is present',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
headerType: computed('changeset.HeaderType', 'headerTypes.firstObject', function() {
|
||||||
|
return this.changeset.HeaderType || this.headerTypes.firstObject;
|
||||||
|
}),
|
||||||
|
|
||||||
|
headerTypeEqualsPresent: equal('headerType', 'Present'),
|
||||||
|
shouldShowValueField: not('headerTypeEqualsPresent'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
change: function(name, changeset, e) {
|
||||||
|
const value = typeof get(e, 'target.value') !== 'undefined' ? e.target.value : e;
|
||||||
|
switch (name) {
|
||||||
|
default:
|
||||||
|
changeset.set(name, value);
|
||||||
|
}
|
||||||
|
changeset.validate();
|
||||||
|
},
|
||||||
|
submit: function(changeset) {
|
||||||
|
this.headerTypes.forEach(prop => {
|
||||||
|
changeset.set(prop, undefined);
|
||||||
|
});
|
||||||
|
// Present is a boolean, whereas all other header types have a value
|
||||||
|
const value = changeset.HeaderType === 'Present' ? true : changeset.Value;
|
||||||
|
changeset.set(changeset.HeaderType, value);
|
||||||
|
|
||||||
|
// this will prevent the changeset from overwriting the
|
||||||
|
// computed properties on the ED object
|
||||||
|
delete changeset._changes.HeaderType;
|
||||||
|
delete changeset._changes.Value;
|
||||||
|
//
|
||||||
|
|
||||||
|
this.repo.persist(changeset);
|
||||||
|
this.onsubmit(changeset.data);
|
||||||
|
|
||||||
|
set(
|
||||||
|
this,
|
||||||
|
'item',
|
||||||
|
this.repo.create({
|
||||||
|
HeaderType: this.headerType,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reset: function(changeset, e) {
|
||||||
|
changeset.rollback();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
{{#if (gt items.length 0)}}
|
||||||
|
<ListCollection
|
||||||
|
class="consul-intention-permission-header-list"
|
||||||
|
@items={{items}}
|
||||||
|
@scroll="native"
|
||||||
|
@cellHeight={{42}}
|
||||||
|
as |item|>
|
||||||
|
<BlockSlot @name="details">
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<Tooltip>
|
||||||
|
Header
|
||||||
|
</Tooltip>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{item.Name}} {{route-match item}}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="actions" as |Actions|>
|
||||||
|
<Actions as |Action|>
|
||||||
|
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
|
||||||
|
<BlockSlot @name="label">
|
||||||
|
Delete
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="confirmation" as |Confirmation|>
|
||||||
|
<Confirmation class="warning">
|
||||||
|
<BlockSlot @name="header">
|
||||||
|
Confirm delete
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="body">
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete this header?
|
||||||
|
</p>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="confirm" as |Confirm|>
|
||||||
|
<Confirm>Delete</Confirm>
|
||||||
|
</BlockSlot>
|
||||||
|
</Confirmation>
|
||||||
|
</BlockSlot>
|
||||||
|
</Action>
|
||||||
|
</Actions>
|
||||||
|
</BlockSlot>
|
||||||
|
</ListCollection>
|
||||||
|
{{/if}}
|
@ -0,0 +1,5 @@
|
|||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: '',
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
@import './skin';
|
||||||
|
@import './layout';
|
||||||
|
.consul-intention-permission-header-list {
|
||||||
|
> ul > li {
|
||||||
|
@extend %list-row-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
.consul-intention-permission-header-list {
|
||||||
|
dt::before {
|
||||||
|
@extend %with-glyph-icon;
|
||||||
|
content: 'H';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,35 +1,83 @@
|
|||||||
{{#if (gt items.length 0)}}
|
{{#if (gt items.length 0)}}
|
||||||
<div class="consul-intention-permission-list">
|
<ListCollection
|
||||||
<ul>
|
class="consul-intention-permission-list{{if (not onclick) ' readonly'}}"
|
||||||
{{#each items as |item|}}
|
@scroll="native"
|
||||||
<li>
|
@items={{items}}
|
||||||
|
@cellHeight={{42}}
|
||||||
|
as |item|>
|
||||||
|
<BlockSlot @name="details">
|
||||||
|
<div onclick={{action (optional onclick) item}}>
|
||||||
<strong class={{concat 'intent-' item.Action}}>{{item.Action}}</strong>
|
<strong class={{concat 'intent-' item.Action}}>{{item.Action}}</strong>
|
||||||
{{#if item.Http.Path}}
|
{{#if (gt item.HTTP.Methods.length 0)}}
|
||||||
<dl class="route-path">
|
<dl class="permission-methods">
|
||||||
<dt>
|
<dt>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{{item.Http.PathType}}
|
Methods
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{item.Http.Path}}
|
{{#each item.HTTP.Methods as |item|}}
|
||||||
|
{{item}}
|
||||||
|
{{/each}}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each item.Http.Header as |item|}}
|
{{#if item.HTTP.Path}}
|
||||||
<dl class="route-header">
|
<dl class="permission-path">
|
||||||
|
<dt>
|
||||||
|
<Tooltip>
|
||||||
|
{{item.HTTP.PathType}}
|
||||||
|
</Tooltip>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{item.HTTP.Path}}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
{{/if}}
|
||||||
|
{{#each item.HTTP.Header as |item|}}
|
||||||
|
<dl class="permission-header">
|
||||||
<dt>
|
<dt>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
Header
|
Header
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{item.Name}} {{route-match item}}
|
{{item.Name}} {{route-match item}}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</li>
|
</div>
|
||||||
{{/each}}
|
</BlockSlot>
|
||||||
</ul>
|
{{#if onclick}}
|
||||||
</div>
|
<BlockSlot @name="actions" as |Actions|>
|
||||||
|
<Actions as |Action|>
|
||||||
|
<Action data-test-edit-action @onclick={{action (optional onclick) item}} @close={{true}}>
|
||||||
|
<BlockSlot @name="label">
|
||||||
|
Edit
|
||||||
|
</BlockSlot>
|
||||||
|
</Action>
|
||||||
|
<Action data-test-delete-action @onclick={{action ondelete item}} class="dangerous">
|
||||||
|
<BlockSlot @name="label">
|
||||||
|
Delete
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="confirmation" as |Confirmation|>
|
||||||
|
<Confirmation class="warning">
|
||||||
|
<BlockSlot @name="header">
|
||||||
|
Confirm delete
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="body">
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete this permission?
|
||||||
|
</p>
|
||||||
|
</BlockSlot>
|
||||||
|
<BlockSlot @name="confirm" as |Confirm|>
|
||||||
|
<Confirm>Delete</Confirm>
|
||||||
|
</BlockSlot>
|
||||||
|
</Confirmation>
|
||||||
|
</BlockSlot>
|
||||||
|
</Action>
|
||||||
|
</Actions>
|
||||||
|
</BlockSlot>
|
||||||
|
{{/if}}
|
||||||
|
</ListCollection>
|
||||||
{{/if}}
|
{{/if}}
|
@ -0,0 +1,36 @@
|
|||||||
|
@import './skin';
|
||||||
|
@import './layout';
|
||||||
|
%list-row-200 {
|
||||||
|
@extend %list-row;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
%list-row-200 .detail {
|
||||||
|
grid-row-start: header !important;
|
||||||
|
grid-row-end: detail !important;
|
||||||
|
align-self: center !important;
|
||||||
|
}
|
||||||
|
%list-row-200 .popover-menu > [type="checkbox"] + label {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
%list-row-200 .popover-menu > [type="checkbox"] + label + div:not(.above) {
|
||||||
|
top: 30px;
|
||||||
|
}
|
||||||
|
%list-row-200 dd {
|
||||||
|
@extend %p2;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-list > ul > li {
|
||||||
|
@extend %list-row-200;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-list:not(.readonly) > ul > li {
|
||||||
|
@extend %with-composite-row-intent;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-list strong {
|
||||||
|
@extend %pill-500;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-list .intent-allow {
|
||||||
|
@extend %pill-allow;
|
||||||
|
}
|
||||||
|
.consul-intention-permission-list .intent-deny {
|
||||||
|
@extend %pill-deny;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
.consul-intention-permission-list {
|
||||||
|
.detail > div {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
.consul-intention-permission-list {
|
||||||
|
dt::before {
|
||||||
|
@extend %with-glyph-icon;
|
||||||
|
}
|
||||||
|
dl.permission-methods dt::before {
|
||||||
|
content: 'M';
|
||||||
|
}
|
||||||
|
dl.permission-path dt::before {
|
||||||
|
content: 'P';
|
||||||
|
}
|
||||||
|
dl.permission-header dt::before {
|
||||||
|
content: 'H';
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,9 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConsulIntentionPermissionList @items={{item.Permissions}} @readonly={{true}} />
|
<ConsulIntentionPermissionList
|
||||||
|
@items={{item.Permissions}}
|
||||||
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,34 +1,69 @@
|
|||||||
{{on-window 'resize' (action "resize") }}
|
<div
|
||||||
{{yield}}
|
class="list-collection list-collection-scroll-{{scroll}}"
|
||||||
<EmberNativeScrollable
|
style={{{style}}}
|
||||||
@tagName="ul"
|
id={{guid}}
|
||||||
@content-size={{_contentSize}}
|
...attributes
|
||||||
@scroll-left={{_scrollLeft}}
|
|
||||||
@scroll-top={{_scrollTop}}
|
|
||||||
@scrollChange={{action "scrollChange"}}
|
|
||||||
@clientSizeChange={{action "clientSizeChange"}}
|
|
||||||
>
|
>
|
||||||
<li></li>
|
{{yield}}
|
||||||
{{~#each _cells as |cell|~}}
|
{{#if (eq scroll 'virtual')}}
|
||||||
<li
|
{{on-window 'resize' (action "resize") }}
|
||||||
data-test-list-row
|
<EmberNativeScrollable
|
||||||
onclick={{action 'click'}} style={{{cell.style}}}
|
@tagName="ul"
|
||||||
class={{if
|
@content-size={{_contentSize}}
|
||||||
(compute (action (or linkable (noop)) cell.item))
|
@scroll-left={{_scrollLeft}}
|
||||||
'linkable'
|
@scroll-top={{_scrollTop}}
|
||||||
}}
|
@scrollChange={{action "scrollChange"}}
|
||||||
>
|
@clientSizeChange={{action "clientSizeChange"}}
|
||||||
<YieldSlot @name="header"><div class="header">{{yield cell.item cell.index}}</div></YieldSlot>
|
>
|
||||||
<YieldSlot @name="details"><div class="detail">{{yield cell.item cell.index}}</div></YieldSlot>
|
<li></li>
|
||||||
<YieldSlot @name="actions"
|
{{~#each _cells as |cell|~}}
|
||||||
@params={{
|
<li
|
||||||
block-params (component 'more-popover-menu' expanded=(if (eq checked cell.index) true false) onchange=(action "change" cell.index))
|
data-test-list-row
|
||||||
|
onclick={{action 'click'}} style={{{cell.style}}}
|
||||||
|
class={{if
|
||||||
|
(compute (action (or linkable (noop)) cell.item))
|
||||||
|
'linkable'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="actions">
|
<YieldSlot @name="header"><div class="header">{{yield cell.item cell.index}}</div></YieldSlot>
|
||||||
{{yield cell.item cell.index}}
|
<YieldSlot @name="details"><div class="detail">{{yield cell.item cell.index}}</div></YieldSlot>
|
||||||
</div>
|
<YieldSlot @name="actions"
|
||||||
</YieldSlot>
|
@params={{
|
||||||
</li>
|
block-params (component 'more-popover-menu' expanded=(if (eq checked cell.index) true false) onchange=(action "change" cell.index))
|
||||||
{{~/each~}}
|
}}
|
||||||
</EmberNativeScrollable>
|
>
|
||||||
|
<div class="actions">
|
||||||
|
{{yield cell.item cell.index}}
|
||||||
|
</div>
|
||||||
|
</YieldSlot>
|
||||||
|
</li>
|
||||||
|
{{~/each~}}
|
||||||
|
</EmberNativeScrollable>
|
||||||
|
{{else}}
|
||||||
|
<ul>
|
||||||
|
<li style="display: none;"></li>
|
||||||
|
{{~#each items as |item index|~}}
|
||||||
|
<li
|
||||||
|
data-test-list-row
|
||||||
|
onclick={{action 'click'}}
|
||||||
|
class={{if
|
||||||
|
(compute (action (or linkable (noop)) item))
|
||||||
|
'linkable'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<YieldSlot @name="header"><div class="header">{{yield item index}}</div></YieldSlot>
|
||||||
|
<YieldSlot @name="details"><div class="detail">{{yield item index}}</div></YieldSlot>
|
||||||
|
<YieldSlot @name="actions"
|
||||||
|
@params={{
|
||||||
|
block-params (component 'more-popover-menu' onchange=(action "change" index))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="actions">
|
||||||
|
{{yield item index}}
|
||||||
|
</div>
|
||||||
|
</YieldSlot>
|
||||||
|
</li>
|
||||||
|
{{~/each~}}
|
||||||
|
</ul>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
@ -9,20 +9,23 @@ const formatItemStyle = PercentageColumns.prototype.formatItemStyle;
|
|||||||
|
|
||||||
export default Component.extend(Slotted, {
|
export default Component.extend(Slotted, {
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
tagName: 'div',
|
tagName: '',
|
||||||
attributeBindings: ['style'],
|
|
||||||
height: 500,
|
height: 500,
|
||||||
cellHeight: 70,
|
cellHeight: 70,
|
||||||
style: style('getStyle'),
|
style: style('getStyle'),
|
||||||
classNames: ['list-collection'],
|
|
||||||
checked: null,
|
checked: null,
|
||||||
|
scroll: 'virtual',
|
||||||
init: function() {
|
init: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.columns = [100];
|
this.columns = [100];
|
||||||
|
this.guid = this.dom.guid(this);
|
||||||
},
|
},
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.actions.resize.apply(this, [{ target: this.dom.viewport() }]);
|
this.$element = this.dom.element(`#${this.guid}`);
|
||||||
|
if (this.scroll === 'virtual') {
|
||||||
|
this.actions.resize.apply(this, [{ target: this.dom.viewport() }]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
didReceiveAttrs: function() {
|
didReceiveAttrs: function() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
@ -41,6 +44,9 @@ export default Component.extend(Slotted, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
getStyle: computed('height', function() {
|
getStyle: computed('height', function() {
|
||||||
|
if (this.scroll !== 'virtual') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
height: get(this, 'height'),
|
height: get(this, 'height'),
|
||||||
};
|
};
|
||||||
@ -49,12 +55,11 @@ export default Component.extend(Slotted, {
|
|||||||
resize: function(e) {
|
resize: function(e) {
|
||||||
// TODO: This top part is very similar to resize in tabular-collection
|
// TODO: This top part is very similar to resize in tabular-collection
|
||||||
// see if it make sense to DRY out
|
// see if it make sense to DRY out
|
||||||
const dom = get(this, 'dom');
|
const $appContent = this.dom.element('main > div');
|
||||||
const $appContent = dom.element('main > div');
|
|
||||||
if ($appContent) {
|
if ($appContent) {
|
||||||
const border = 1;
|
const border = 1;
|
||||||
const rect = this.element.getBoundingClientRect();
|
const rect = this.$element.getBoundingClientRect();
|
||||||
const $footer = dom.element('footer[role="contentinfo"]');
|
const $footer = this.dom.element('footer[role="contentinfo"]');
|
||||||
const space = rect.top + $footer.clientHeight + border;
|
const space = rect.top + $footer.clientHeight + border;
|
||||||
const height = e.target.innerHeight - space;
|
const height = e.target.innerHeight - space;
|
||||||
this.set('height', Math.max(0, height));
|
this.set('height', Math.max(0, height));
|
||||||
|
@ -36,6 +36,7 @@ as |components|}}
|
|||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
|
||||||
<MenuPanel @position={{position}} id={{aria.controls}} aria-labelledby={{aria.labelledBy}} aria-expanded={{aria.expanded}} as |menu|>
|
<MenuPanel @position={{position}} id={{aria.controls}} aria-labelledby={{aria.labelledBy}} aria-expanded={{aria.expanded}} as |menu|>
|
||||||
|
<Ref @target={{this}} @name="menu" @value={{menu}} />
|
||||||
<BlockSlot @name="controls">
|
<BlockSlot @name="controls">
|
||||||
<input type="checkbox" id={{concat 'popover-menu-' guid '-'}} />
|
<input type="checkbox" id={{concat 'popover-menu-' guid '-'}} />
|
||||||
{{#each submenus as |sub|}}
|
{{#each submenus as |sub|}}
|
||||||
|
@ -32,7 +32,10 @@
|
|||||||
role="menuitem"
|
role="menuitem"
|
||||||
aria-selected={{if selected 'true'}}
|
aria-selected={{if selected 'true'}}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
onclick={{action (or this.onclick (noop))}}>
|
onclick={{queue
|
||||||
|
(action (or this.onclick (noop)))
|
||||||
|
(action (if this.close menu.clickTrigger (noop)))
|
||||||
|
}}>
|
||||||
<YieldSlot @name="label">
|
<YieldSlot @name="label">
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</YieldSlot>
|
</YieldSlot>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import Slotted from 'block-slots';
|
|
||||||
|
|
||||||
export default Component.extend(Slotted, {
|
export default Component.extend({
|
||||||
tagName: '',
|
tagName: '',
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { helper } from '@ember/component/helper';
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
export default helper(function routeMatch([item], hash) {
|
export default helper(function routeMatch([item], hash) {
|
||||||
const keys = Object.keys(item.data || item);
|
const prop = ['Present', 'Exact', 'Prefix', 'Suffix', 'Regex'].find(
|
||||||
switch (true) {
|
prop => typeof item[prop] !== 'undefined'
|
||||||
case keys.includes('Present'):
|
);
|
||||||
|
|
||||||
|
switch (prop) {
|
||||||
|
case 'Present':
|
||||||
return `${item.Invert ? `NOT ` : ``}present`;
|
return `${item.Invert ? `NOT ` : ``}present`;
|
||||||
case keys.includes('Exact'):
|
case 'Exact':
|
||||||
return `${item.Invert ? `NOT ` : ``}exactly matching "${item.Exact}"`;
|
return `${item.Invert ? `NOT ` : ``}exactly matching "${item.Exact}"`;
|
||||||
case keys.includes('Prefix'):
|
case 'Prefix':
|
||||||
return `${item.Invert ? `NOT ` : ``}prefixed by "${item.Prefix}"`;
|
return `${item.Invert ? `NOT ` : ``}prefixed by "${item.Prefix}"`;
|
||||||
case keys.includes('Suffix'):
|
case 'Suffix':
|
||||||
return `${item.Invert ? `NOT ` : ``}suffixed by "${item.Suffix}"`;
|
return `${item.Invert ? `NOT ` : ``}suffixed by "${item.Suffix}"`;
|
||||||
case keys.includes('Regex'):
|
case 'Regex':
|
||||||
return `${item.Invert ? `NOT ` : ``}matching the regex "${item.Regex}"`;
|
return `${item.Invert ? `NOT ` : ``}matching the regex "${item.Regex}"`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
|
import { computed } from '@ember/object';
|
||||||
|
import { or } from '@ember/object/computed';
|
||||||
import attr from 'ember-data/attr';
|
import attr from 'ember-data/attr';
|
||||||
|
|
||||||
import Fragment from 'ember-data-model-fragments/fragment';
|
import Fragment from 'ember-data-model-fragments/fragment';
|
||||||
|
|
||||||
|
export const schema = {
|
||||||
|
Name: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
HeaderType: {
|
||||||
|
allowedValues: ['Exact', 'Prefix', 'Suffix', 'Regex', 'Present'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default Fragment.extend({
|
export default Fragment.extend({
|
||||||
Name: attr('string'),
|
Name: attr('string'),
|
||||||
|
|
||||||
@ -9,6 +20,10 @@ export default Fragment.extend({
|
|||||||
Prefix: attr('string'),
|
Prefix: attr('string'),
|
||||||
Suffix: attr('string'),
|
Suffix: attr('string'),
|
||||||
Regex: attr('string'),
|
Regex: attr('string'),
|
||||||
Present: attr('boolean'),
|
Present: attr(), // this is a boolean but we don't want it to automatically be set to false
|
||||||
Invert: attr('boolean'),
|
|
||||||
|
Value: or(...schema.HeaderType.allowedValues),
|
||||||
|
HeaderType: computed(...schema.HeaderType.allowedValues, function() {
|
||||||
|
return schema.HeaderType.allowedValues.find(prop => typeof this[prop] !== 'undefined');
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,15 @@ import { or } from '@ember/object/computed';
|
|||||||
import Fragment from 'ember-data-model-fragments/fragment';
|
import Fragment from 'ember-data-model-fragments/fragment';
|
||||||
import { fragmentArray, array } from 'ember-data-model-fragments/attributes';
|
import { fragmentArray, array } from 'ember-data-model-fragments/attributes';
|
||||||
|
|
||||||
const pathProps = ['PathPrefix', 'PathExact', 'PathRegex'];
|
export const schema = {
|
||||||
|
PathType: {
|
||||||
|
allowedValues: ['PathPrefix', 'PathExact', 'PathRegex'],
|
||||||
|
},
|
||||||
|
Methods: {
|
||||||
|
allowedValues: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default Fragment.extend({
|
export default Fragment.extend({
|
||||||
PathExact: attr('string'),
|
PathExact: attr('string'),
|
||||||
PathPrefix: attr('string'),
|
PathPrefix: attr('string'),
|
||||||
@ -14,8 +22,8 @@ export default Fragment.extend({
|
|||||||
Header: fragmentArray('intention-permission-http-header'),
|
Header: fragmentArray('intention-permission-http-header'),
|
||||||
Methods: array('string'),
|
Methods: array('string'),
|
||||||
|
|
||||||
Path: or(...pathProps),
|
Path: or(...schema.PathType.allowedValues),
|
||||||
PathType: computed(...pathProps, function() {
|
PathType: computed(...schema.PathType.allowedValues, function() {
|
||||||
return pathProps.find(prop => typeof this[prop] === 'string');
|
return schema.PathType.allowedValues.find(prop => typeof this[prop] === 'string');
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,16 @@ import attr from 'ember-data/attr';
|
|||||||
import Fragment from 'ember-data-model-fragments/fragment';
|
import Fragment from 'ember-data-model-fragments/fragment';
|
||||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||||
|
|
||||||
|
export const schema = {
|
||||||
|
Action: {
|
||||||
|
defaultValue: 'allow',
|
||||||
|
allowedValues: ['allow', 'deny'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default Fragment.extend({
|
export default Fragment.extend({
|
||||||
Action: attr('string', { defaultValue: 'allow' }),
|
Action: attr('string', {
|
||||||
Http: fragment('intention-permission-http'),
|
defaultValue: schema.Action.defaultValue,
|
||||||
|
}),
|
||||||
|
HTTP: fragment('intention-permission-http'),
|
||||||
});
|
});
|
||||||
|
@ -10,17 +10,17 @@ export default Model.extend({
|
|||||||
[PRIMARY_KEY]: attr('string'),
|
[PRIMARY_KEY]: attr('string'),
|
||||||
[SLUG_KEY]: attr('string'),
|
[SLUG_KEY]: attr('string'),
|
||||||
Description: attr('string'),
|
Description: attr('string'),
|
||||||
SourceNS: attr('string'),
|
SourceNS: attr('string', { defaultValue: 'default' }),
|
||||||
SourceName: attr('string', { defaultValue: '*' }),
|
SourceName: attr('string', { defaultValue: '*' }),
|
||||||
DestinationName: attr('string', { defaultValue: '*' }),
|
DestinationName: attr('string', { defaultValue: '*' }),
|
||||||
DestinationNS: attr('string'),
|
DestinationNS: attr('string', { defaultValue: 'default' }),
|
||||||
Precedence: attr('number'),
|
Precedence: attr('number'),
|
||||||
Permissions: fragmentArray('intention-permission'),
|
Permissions: fragmentArray('intention-permission'),
|
||||||
SourceType: attr('string', { defaultValue: 'consul' }),
|
SourceType: attr('string', { defaultValue: 'consul' }),
|
||||||
Action: attr('string'),
|
Action: attr('string'),
|
||||||
Meta: attr(),
|
Meta: attr(),
|
||||||
Legacy: attr('boolean', { defaultValue: true }),
|
|
||||||
LegacyID: attr('string'),
|
LegacyID: attr('string'),
|
||||||
|
Legacy: attr('boolean', { defaultValue: true }),
|
||||||
|
|
||||||
IsManagedByCRD: computed('Meta', function() {
|
IsManagedByCRD: computed('Meta', function() {
|
||||||
const meta = Object.entries(this.Meta || {}).find(
|
const meta = Object.entries(this.Meta || {}).find(
|
||||||
@ -29,7 +29,7 @@ export default Model.extend({
|
|||||||
return typeof meta !== 'undefined';
|
return typeof meta !== 'undefined';
|
||||||
}),
|
}),
|
||||||
IsEditable: computed('Legacy', 'IsManagedByCRD', function() {
|
IsEditable: computed('Legacy', 'IsManagedByCRD', function() {
|
||||||
return this.Legacy && !this.IsManagedByCRD;
|
return !this.IsManagedByCRD;
|
||||||
}),
|
}),
|
||||||
SyncTime: attr('number'),
|
SyncTime: attr('number'),
|
||||||
Datacenter: attr('string'),
|
Datacenter: attr('string'),
|
||||||
|
@ -6,7 +6,7 @@ export default Route.extend({
|
|||||||
nspace: '*',
|
nspace: '*',
|
||||||
dc: this.paramsFor('dc').dc,
|
dc: this.paramsFor('dc').dc,
|
||||||
service: this.paramsFor('dc.services.show').name,
|
service: this.paramsFor('dc.services.show').name,
|
||||||
src: params.intention,
|
src: params.intention_id,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
|
@ -44,16 +44,24 @@ export default Serializer.extend({
|
|||||||
query
|
query
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
respondForCreateRecord: function(respond, serialized, data) {
|
||||||
|
const slugKey = this.slugKey;
|
||||||
|
const primaryKey = this.primaryKey;
|
||||||
|
return respond((headers, body) => {
|
||||||
|
body = data;
|
||||||
|
body.ID = this
|
||||||
|
.uri`${serialized.SourceNS}:${serialized.SourceName}:${serialized.DestinationNS}:${serialized.DestinationName}`;
|
||||||
|
return this.fingerprint(primaryKey, slugKey, body.Datacenter)(body);
|
||||||
|
});
|
||||||
|
},
|
||||||
respondForUpdateRecord: function(respond, serialized, data) {
|
respondForUpdateRecord: function(respond, serialized, data) {
|
||||||
return this._super(
|
const slugKey = this.slugKey;
|
||||||
cb =>
|
const primaryKey = this.primaryKey;
|
||||||
respond((headers, body) => {
|
return respond((headers, body) => {
|
||||||
body.LegacyID = body.ID;
|
body = data;
|
||||||
body.ID = serialized.ID;
|
body.LegacyID = body.ID;
|
||||||
return cb(headers, body);
|
body.ID = serialized.ID;
|
||||||
}),
|
return this.fingerprint(primaryKey, slugKey, body.Datacenter)(body);
|
||||||
serialized,
|
});
|
||||||
data
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
51
ui-v2/app/services/change.js
Normal file
51
ui-v2/app/services/change.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import Service, { inject as service } from '@ember/service';
|
||||||
|
|
||||||
|
import lookupValidator from 'ember-changeset-validations';
|
||||||
|
import { Changeset as createChangeset } from 'ember-changeset';
|
||||||
|
|
||||||
|
import Changeset from 'consul-ui/utils/form/changeset';
|
||||||
|
|
||||||
|
import intentionPermissionValidator from 'consul-ui/validations/intention-permission';
|
||||||
|
import intentionPermissionHttpHeaderValidator from 'consul-ui/validations/intention-permission-http-header';
|
||||||
|
|
||||||
|
const validators = {
|
||||||
|
'intention-permission': intentionPermissionValidator,
|
||||||
|
'intention-permission-http-header': intentionPermissionHttpHeaderValidator,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
schema: service('schema'),
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this._validators = new Map();
|
||||||
|
},
|
||||||
|
willDestroy: function() {
|
||||||
|
this._validators = null;
|
||||||
|
},
|
||||||
|
changesetFor: function(modelName, model, options = {}) {
|
||||||
|
const validator = this.validatorFor(modelName, options);
|
||||||
|
let changeset;
|
||||||
|
if (validator) {
|
||||||
|
let validatorFunc = validator;
|
||||||
|
if (typeof validator !== 'function') {
|
||||||
|
validatorFunc = lookupValidator(validator);
|
||||||
|
}
|
||||||
|
changeset = createChangeset(model, validatorFunc, validator, { changeset: Changeset });
|
||||||
|
} else {
|
||||||
|
changeset = createChangeset(model);
|
||||||
|
}
|
||||||
|
return changeset;
|
||||||
|
},
|
||||||
|
validatorFor: function(modelName, options = {}) {
|
||||||
|
if (!this._validators.has(modelName)) {
|
||||||
|
const factory = validators[modelName];
|
||||||
|
let validator;
|
||||||
|
if (typeof factory !== 'undefined') {
|
||||||
|
validator = factory(this.schema);
|
||||||
|
}
|
||||||
|
this._validators.set(modelName, validator);
|
||||||
|
}
|
||||||
|
return this._validators.get(modelName);
|
||||||
|
},
|
||||||
|
});
|
@ -2,6 +2,7 @@ import Service, { inject as service } from '@ember/service';
|
|||||||
import { assert } from '@ember/debug';
|
import { assert } from '@ember/debug';
|
||||||
import { typeOf } from '@ember/utils';
|
import { typeOf } from '@ember/utils';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
import { isChangeset } from 'validated-changeset';
|
||||||
|
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
getModelName: function() {
|
getModelName: function() {
|
||||||
@ -67,6 +68,13 @@ export default Service.extend({
|
|||||||
return this.store.createRecord(this.getModelName(), obj);
|
return this.store.createRecord(this.getModelName(), obj);
|
||||||
},
|
},
|
||||||
persist: function(item) {
|
persist: function(item) {
|
||||||
|
// workaround for saving changesets that contain fragments
|
||||||
|
// firstly commit the changes down onto the object if
|
||||||
|
// its a changeset, then save as a normal object
|
||||||
|
if (isChangeset(item)) {
|
||||||
|
item.execute();
|
||||||
|
item = item.data;
|
||||||
|
}
|
||||||
return item.save();
|
return item.save();
|
||||||
},
|
},
|
||||||
remove: function(obj) {
|
remove: function(obj) {
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
|
|
||||||
|
const modelName = 'intention-permission-http-header';
|
||||||
|
export default RepositoryService.extend({
|
||||||
|
getModelName: function() {
|
||||||
|
return modelName;
|
||||||
|
},
|
||||||
|
create: function(obj = {}) {
|
||||||
|
return this.store.createFragment(this.getModelName(), obj);
|
||||||
|
},
|
||||||
|
persist: function(item) {
|
||||||
|
return item.execute();
|
||||||
|
},
|
||||||
|
});
|
18
ui-v2/app/services/repository/intention-permission.js
Normal file
18
ui-v2/app/services/repository/intention-permission.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
|
const modelName = 'intention-permission';
|
||||||
|
export default RepositoryService.extend({
|
||||||
|
getModelName: function() {
|
||||||
|
return modelName;
|
||||||
|
},
|
||||||
|
create: function(obj = {}) {
|
||||||
|
// intention-permission and intention-permission-http
|
||||||
|
// are currently treated as one and the same
|
||||||
|
return this.store.createFragment(this.getModelName(), {
|
||||||
|
...obj,
|
||||||
|
HTTP: this.store.createFragment('intention-permission-http', obj.HTTP || {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
persist: function(item) {
|
||||||
|
return item.execute();
|
||||||
|
},
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
import { set, get } from '@ember/object';
|
||||||
import RepositoryService from 'consul-ui/services/repository';
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
import { PRIMARY_KEY } from 'consul-ui/models/intention';
|
import { PRIMARY_KEY } from 'consul-ui/models/intention';
|
||||||
const modelName = 'intention';
|
const modelName = 'intention';
|
||||||
@ -15,6 +16,19 @@ export default RepositoryService.extend({
|
|||||||
...obj,
|
...obj,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
persist: function(obj) {
|
||||||
|
return this._super(...arguments).then(res => {
|
||||||
|
// if Action is set it means we are an l4 type intention
|
||||||
|
// we don't delete these at a UI level incase the user
|
||||||
|
// would like to switch backwards and forwards between
|
||||||
|
// allow/deny/l7 in the forms, but once its been saved
|
||||||
|
// to the backend we then delete them
|
||||||
|
if (get(res, 'Action.length')) {
|
||||||
|
set(res, 'Permissions', []);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
},
|
||||||
findByService: function(slug, dc, nspace, configuration = {}) {
|
findByService: function(slug, dc, nspace, configuration = {}) {
|
||||||
const query = {
|
const query = {
|
||||||
dc: dc,
|
dc: dc,
|
||||||
|
10
ui-v2/app/services/schema.js
Normal file
10
ui-v2/app/services/schema.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import Service from '@ember/service';
|
||||||
|
import { schema as intentionPermissionSchema } from 'consul-ui/models/intention-permission';
|
||||||
|
import { schema as intentionPermissionHttpSchema } from 'consul-ui/models/intention-permission-http';
|
||||||
|
import { schema as intentionPermissionHttpHeaderSchema } from 'consul-ui/models/intention-permission-http-header';
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
'intention-permission': intentionPermissionSchema,
|
||||||
|
'intention-permission-http': intentionPermissionHttpSchema,
|
||||||
|
'intention-permission-http-header': intentionPermissionHttpHeaderSchema,
|
||||||
|
});
|
@ -54,6 +54,15 @@
|
|||||||
@import './components/footer';
|
@import './components/footer';
|
||||||
|
|
||||||
/**/
|
/**/
|
||||||
@import './components/consul-intention-list';
|
|
||||||
@import './components/consul-intention-permission-list';
|
/**
|
||||||
@import './components/consul-intention-fieldsets';
|
* Migration: We are migrating our consul-* styles to use colocated styles
|
||||||
|
* consul-* component styles should be moved or added under here
|
||||||
|
* when convienient
|
||||||
|
**/
|
||||||
|
|
||||||
|
@import 'consul-ui/components/consul-intention-list';
|
||||||
|
@import 'consul-ui/components/consul-intention-form/fieldsets';
|
||||||
|
@import 'consul-ui/components/consul-intention-permission-list';
|
||||||
|
@import 'consul-ui/components/consul-intention-permission-form';
|
||||||
|
@import 'consul-ui/components/consul-intention-permission-header-list';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
button[type='submit'],
|
button[type='submit'],
|
||||||
|
button.type-submit,
|
||||||
a.type-create {
|
a.type-create {
|
||||||
@extend %primary-button;
|
@extend %primary-button;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
@import './consul-intention-fieldsets/index';
|
|
||||||
.consul-intention-fieldsets {
|
|
||||||
@extend %consul-intention-fieldsets;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
%consul-intention-fieldsets [role='radiogroup'] {
|
|
||||||
overflow: visible !important;
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 12px;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(270px, 370px));
|
|
||||||
}
|
|
||||||
%consul-intention-fieldsets .radio-card {
|
|
||||||
@extend %radio-card-with-icon;
|
|
||||||
}
|
|
||||||
%consul-intention-fieldsets .radio-card header > * {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
%consul-intention-fieldsets .value-allow > :last-child::before {
|
|
||||||
@extend %with-arrow-right-color-icon, %as-pseudo;
|
|
||||||
}
|
|
||||||
%consul-intention-fieldsets .value-deny > :last-child::before {
|
|
||||||
@extend %with-deny-color-icon, %as-pseudo;
|
|
||||||
}
|
|
||||||
%consul-intention-fieldsets .radio-card header span.code::before {
|
|
||||||
@extend %with-deny-color-icon, %as-pseudo;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
@import './consul-intention-list/index';
|
|
||||||
.consul-intention-list {
|
|
||||||
@extend %consul-intention-list;
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
@import './skin';
|
|
||||||
@import './layout';
|
|
||||||
|
|
||||||
%consul-intention-list td.source,
|
|
||||||
%consul-intention-list td.destination {
|
|
||||||
@extend %tbody-th;
|
|
||||||
}
|
|
||||||
%consul-intention-list td strong {
|
|
||||||
@extend %pill-700;
|
|
||||||
}
|
|
||||||
%consul-intention-list td.intent-allow strong {
|
|
||||||
@extend %pill-allow;
|
|
||||||
}
|
|
||||||
%consul-intention-list td.intent-deny strong {
|
|
||||||
@extend %pill-deny;
|
|
||||||
}
|
|
||||||
%consul-intention-list td.intent-l7-rules strong {
|
|
||||||
@extend %pill-l7;
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
%consul-intention-list td {
|
|
||||||
height: 59px;
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:nth-child(1) {
|
|
||||||
width: calc(30% - 50px);
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:nth-child(2) {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:nth-child(3) {
|
|
||||||
width: calc(30% - 50px);
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:nth-child(4) {
|
|
||||||
width: calc(40% - 220px);
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:nth-child(5) {
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
%consul-intention-list tr > *:last-child {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media #{$--lt-horizontal-nav} {
|
|
||||||
%consul-intention-list tr > :not(.source):not(.destination):not(.intent) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
%consul-intention-list td.permissions {
|
|
||||||
color: $blue-500;
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
@import './consul-intention-permission-list/index';
|
|
||||||
.consul-intention-permission-list {
|
|
||||||
@extend %consul-intention-permission-list;
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
@import './skin';
|
|
||||||
@import './layout';
|
|
||||||
%consul-intention-permission-list > ul > li {
|
|
||||||
@extend %list-row, %list-row-detail;
|
|
||||||
}
|
|
||||||
%consul-intention-permission-list strong {
|
|
||||||
@extend %pill-500;
|
|
||||||
}
|
|
||||||
%consul-intention-permission-list .intent-allow {
|
|
||||||
@extend %pill-allow;
|
|
||||||
}
|
|
||||||
%consul-intention-permission-list .intent-deny {
|
|
||||||
@extend %pill-deny;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
%consul-intention-permission-list strong {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
%consul-intention-permission-list dt::before {
|
|
||||||
@extend %with-glyph-icon;
|
|
||||||
}
|
|
||||||
%consul-intention-permission-list dl.route-path dt::before {
|
|
||||||
content: 'P';
|
|
||||||
}
|
|
||||||
%consul-intention-permission-list dl.route-header dt::before {
|
|
||||||
content: 'H';
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
%flash-message {
|
%flash-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 6;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0 15%;
|
margin: 0 15%;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ label span {
|
|||||||
%main-content form {
|
%main-content form {
|
||||||
@extend %form;
|
@extend %form;
|
||||||
}
|
}
|
||||||
%form span.label {
|
span.label {
|
||||||
@extend %form-element-label;
|
@extend %form-element-label;
|
||||||
}
|
}
|
||||||
%form table,
|
%form table,
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
%icon-definition > dt {
|
%icon-definition > * {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
%icon-definition > dd {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
%icon-definition > dt > * {
|
%icon-definition > dt > * {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
%icon-definition > dd {
|
|
||||||
margin-left: 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
.list-collection {
|
.list-collection {
|
||||||
@extend %list-collection;
|
@extend %list-collection;
|
||||||
}
|
}
|
||||||
%list-collection {
|
.list-collection-scroll-virtual {
|
||||||
|
@extend %list-collection-scroll-virtual;
|
||||||
|
}
|
||||||
|
%list-collection-scroll-virtual {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
%list-collection > ul {
|
%list-collection > ul {
|
||||||
border-top: 1px solid $gray-200;
|
border-top: 1px solid $gray-200;
|
||||||
overflow-x: hidden !important;
|
}
|
||||||
|
%list-collection > ul > li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
%list-collection-scroll-virtual > ul {
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
%list-collection > ul > li:nth-child(2) .with-feedback p {
|
%list-collection > ul > li:nth-child(2) .with-feedback p {
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
import { get, set, computed } from '@ember/object';
|
import { get, set } from '@ember/object';
|
||||||
import Changeset from 'ember-changeset';
|
import { Changeset as createChangeset } from 'ember-changeset';
|
||||||
|
import Changeset from 'consul-ui/utils/form/changeset';
|
||||||
import lookupValidator from 'ember-changeset-validations';
|
import lookupValidator from 'ember-changeset-validations';
|
||||||
|
|
||||||
// Keep these here for now so forms are easy to make
|
// Keep these here for now so forms are easy to make
|
||||||
// TODO: Probably move this to utils/form/parse-element-name
|
// TODO: Probably move this to utils/form/parse-element-name
|
||||||
import parseElementName from 'consul-ui/utils/get-form-name-property';
|
import parseElementName from 'consul-ui/utils/get-form-name-property';
|
||||||
// TODO: Currently supporting ember-data nicely like this
|
|
||||||
// Unfortunately since post-ember 2.18, the only way to get this to work
|
|
||||||
// is to hang stuff off the prototype (which then makes it available everywhere)
|
|
||||||
// we should either try and figure out another way of doing this, or move this code
|
|
||||||
// somewhere where it is more 'global' like an initializer
|
|
||||||
Changeset.prototype.isSaving = computed('data.isSaving', function() {
|
|
||||||
return this.data.isSaving;
|
|
||||||
});
|
|
||||||
const defaultChangeset = function(data, validators) {
|
const defaultChangeset = function(data, validators) {
|
||||||
return new Changeset(data, lookupValidator(validators), validators);
|
return createChangeset(data, lookupValidator(validators), validators, { changeset: Changeset });
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Form builder/Form factory
|
* Form builder/Form factory
|
||||||
|
38
ui-v2/app/utils/form/changeset.js
Normal file
38
ui-v2/app/utils/form/changeset.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { get } from '@ember/object';
|
||||||
|
import { EmberChangeset as Changeset } from 'ember-changeset';
|
||||||
|
const CHANGES = '_changes';
|
||||||
|
export default class extends Changeset {
|
||||||
|
pushObject(prop, value) {
|
||||||
|
let val;
|
||||||
|
if (typeof get(this, `${CHANGES}.${prop}`) === 'undefined') {
|
||||||
|
val = get(this, `data.${prop}`);
|
||||||
|
if (!val) {
|
||||||
|
val = [];
|
||||||
|
} else {
|
||||||
|
val = val.toArray();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val = this.get(prop).slice(0);
|
||||||
|
}
|
||||||
|
val.push(value);
|
||||||
|
this.set(`${prop}`, val);
|
||||||
|
}
|
||||||
|
removeObject(prop, value) {
|
||||||
|
let val;
|
||||||
|
if (typeof get(this, `${CHANGES}.${prop}`) === 'undefined') {
|
||||||
|
val = get(this, `data.${prop}`);
|
||||||
|
if (typeof val === 'undefined') {
|
||||||
|
val = [];
|
||||||
|
} else {
|
||||||
|
val = val.toArray();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val = this.get(prop).slice(0);
|
||||||
|
}
|
||||||
|
const pos = val.indexOf(value);
|
||||||
|
if (pos !== -1) {
|
||||||
|
val.splice(pos, 1);
|
||||||
|
}
|
||||||
|
this.set(`${prop}`, val);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { validatePresence } from 'ember-changeset-validations/validators';
|
||||||
|
import validateSometimes from 'ember-changeset-conditional-validations/validators/sometimes';
|
||||||
|
export default schema => ({
|
||||||
|
Name: [validatePresence(true)],
|
||||||
|
Value: validateSometimes([validatePresence(true)], function() {
|
||||||
|
return this.get('HeaderType') !== 'Present';
|
||||||
|
}),
|
||||||
|
});
|
29
ui-v2/app/validations/intention-permission.js
Normal file
29
ui-v2/app/validations/intention-permission.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
validateInclusion,
|
||||||
|
validatePresence,
|
||||||
|
validateFormat,
|
||||||
|
} from 'ember-changeset-validations/validators';
|
||||||
|
import validateSometimes from 'ember-changeset-conditional-validations/validators/sometimes';
|
||||||
|
|
||||||
|
const name = 'intention-permission';
|
||||||
|
export default schema => ({
|
||||||
|
'*': validateSometimes([validatePresence(true)], function() {
|
||||||
|
const methods = this.get('HTTP.Methods') || [];
|
||||||
|
const headers = this.get('HTTP.Header') || [];
|
||||||
|
const pathType = this.get('HTTP.PathType') || 'NoPath';
|
||||||
|
const path = this.get('HTTP.Path') || '';
|
||||||
|
const isValid = [
|
||||||
|
methods.length !== 0,
|
||||||
|
headers.length !== 0,
|
||||||
|
pathType !== 'NoPath' && path !== '',
|
||||||
|
].includes(true);
|
||||||
|
return !isValid;
|
||||||
|
}),
|
||||||
|
Action: [validateInclusion({ in: schema[name].Action.allowedValues })],
|
||||||
|
HTTP: {
|
||||||
|
Path: validateSometimes([validateFormat({ regex: /^\// })], function() {
|
||||||
|
const pathType = this.get('HTTP.PathType');
|
||||||
|
return typeof pathType !== 'undefined' && pathType !== 'NoPath';
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,19 @@
|
|||||||
import { validatePresence, validateLength } from 'ember-changeset-validations/validators';
|
import { validatePresence, validateLength } from 'ember-changeset-validations/validators';
|
||||||
|
import validateSometimes from 'ember-changeset-conditional-validations/validators/sometimes';
|
||||||
export default {
|
export default {
|
||||||
|
'*': validateSometimes([validatePresence(true)], function() {
|
||||||
|
const action = this.get('Action') || '';
|
||||||
|
const permissions = this.get('Permissions') || [];
|
||||||
|
if (action === '' && permissions.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
SourceName: [validatePresence(true), validateLength({ min: 1 })],
|
SourceName: [validatePresence(true), validateLength({ min: 1 })],
|
||||||
DestinationName: [validatePresence(true), validateLength({ min: 1 })],
|
DestinationName: [validatePresence(true), validateLength({ min: 1 })],
|
||||||
Action: validatePresence(true),
|
Permissions: [
|
||||||
|
validateSometimes([validateLength({ min: 1 })], function(changes, content) {
|
||||||
|
return !this.get('Action');
|
||||||
|
}),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
@ -68,7 +68,8 @@
|
|||||||
"css.escape": "^1.5.1",
|
"css.escape": "^1.5.1",
|
||||||
"dart-sass": "^1.25.0",
|
"dart-sass": "^1.25.0",
|
||||||
"ember-auto-import": "^1.5.3",
|
"ember-auto-import": "^1.5.3",
|
||||||
"ember-changeset-validations": "^3.0.2",
|
"ember-changeset-conditional-validations": "^0.6.0",
|
||||||
|
"ember-changeset-validations": "^3.9.0",
|
||||||
"ember-cli": "~3.20.2",
|
"ember-cli": "~3.20.2",
|
||||||
"ember-cli-app-version": "^3.2.0",
|
"ember-cli-app-version": "^3.2.0",
|
||||||
"ember-cli-autoprefixer": "^0.8.1",
|
"ember-cli-autoprefixer": "^0.8.1",
|
||||||
|
@ -33,7 +33,7 @@ Feature: dc / intentions / create: Intention Create
|
|||||||
# Specifically set deny
|
# Specifically set deny
|
||||||
And I click "[value=deny]"
|
And I click "[value=deny]"
|
||||||
And I submit
|
And I submit
|
||||||
Then a POST request was made to "/v1/connect/intentions?dc=datacenter" from yaml
|
Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" from yaml
|
||||||
---
|
---
|
||||||
body:
|
body:
|
||||||
SourceName: web
|
SourceName: web
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
Feature: dc / intentions / deleting: Deleting items with confirmations, success and error notifications
|
Feature: dc / intentions / deleting: Deleting items with confirmations, success and error notifications
|
||||||
Background:
|
Background:
|
||||||
Given 1 datacenter model with the value "datacenter"
|
Given 1 datacenter model with the value "datacenter"
|
||||||
Scenario: Deleting a intention model from the intention listing page
|
And 1 intention model from yaml
|
||||||
Given 1 intention model from yaml
|
|
||||||
---
|
---
|
||||||
|
SourceNS: default
|
||||||
SourceName: name
|
SourceName: name
|
||||||
|
DestinationNS: default
|
||||||
|
DestinationName: destination
|
||||||
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
ID: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
||||||
Meta: ~
|
Meta: ~
|
||||||
---
|
---
|
||||||
|
Scenario: Deleting a intention model from the intention listing page
|
||||||
When I visit the intentions page for yaml
|
When I visit the intentions page for yaml
|
||||||
---
|
---
|
||||||
dc: datacenter
|
dc: datacenter
|
||||||
@ -16,7 +19,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
|
|||||||
And I click actions on the intentions
|
And I click actions on the intentions
|
||||||
And I click delete on the intentions
|
And I click delete on the intentions
|
||||||
And I click confirmDelete on the intentions
|
And I click confirmDelete on the intentions
|
||||||
Then a DELETE request was made to "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter"
|
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter"
|
||||||
And "[data-notification]" has the "notification-delete" class
|
And "[data-notification]" has the "notification-delete" class
|
||||||
And "[data-notification]" has the "success" class
|
And "[data-notification]" has the "success" class
|
||||||
Scenario: Deleting an intention from the intention detail page
|
Scenario: Deleting an intention from the intention detail page
|
||||||
@ -27,7 +30,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
|
|||||||
---
|
---
|
||||||
And I click delete
|
And I click delete
|
||||||
And I click confirmDelete
|
And I click confirmDelete
|
||||||
Then a DELETE request was made to "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter"
|
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter"
|
||||||
And "[data-notification]" has the "notification-delete" class
|
And "[data-notification]" has the "notification-delete" class
|
||||||
And "[data-notification]" has the "success" class
|
And "[data-notification]" has the "success" class
|
||||||
Scenario: Deleting an intention from the intention detail page and getting an error
|
Scenario: Deleting an intention from the intention detail page and getting an error
|
||||||
@ -36,7 +39,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
|
|||||||
dc: datacenter
|
dc: datacenter
|
||||||
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
||||||
---
|
---
|
||||||
Given the url "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter" responds with a 500 status
|
Given the url "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" responds with a 500 status
|
||||||
And I click delete
|
And I click delete
|
||||||
And I click confirmDelete
|
And I click confirmDelete
|
||||||
And "[data-notification]" has the "notification-update" class
|
And "[data-notification]" has the "notification-update" class
|
||||||
@ -47,7 +50,7 @@ Feature: dc / intentions / deleting: Deleting items with confirmations, success
|
|||||||
dc: datacenter
|
dc: datacenter
|
||||||
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
intention: ee52203d-989f-4f7a-ab5a-2bef004164ca
|
||||||
---
|
---
|
||||||
Given the url "/v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter" responds with from yaml
|
Given the url "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=datacenter" responds with from yaml
|
||||||
---
|
---
|
||||||
status: 500
|
status: 500
|
||||||
body: "duplicate intention found:"
|
body: "duplicate intention found:"
|
||||||
|
@ -4,6 +4,10 @@ Feature: dc / intentions / update: Intention Update
|
|||||||
Given 1 datacenter model with the value "datacenter"
|
Given 1 datacenter model with the value "datacenter"
|
||||||
And 1 intention model from yaml
|
And 1 intention model from yaml
|
||||||
---
|
---
|
||||||
|
SourceNS: default
|
||||||
|
SourceName: web
|
||||||
|
DestinationNS: default
|
||||||
|
DestinationName: db
|
||||||
ID: intention-id
|
ID: intention-id
|
||||||
---
|
---
|
||||||
When I visit the intention page for yaml
|
When I visit the intention page for yaml
|
||||||
@ -20,7 +24,7 @@ Feature: dc / intentions / update: Intention Update
|
|||||||
---
|
---
|
||||||
And I click "[value=[Action]]"
|
And I click "[value=[Action]]"
|
||||||
And I submit
|
And I submit
|
||||||
Then a PUT request was made to "/v1/connect/intentions/intention-id?dc=datacenter" with the body from yaml
|
Then a PUT request was made to "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" from yaml
|
||||||
---
|
---
|
||||||
Description: [Description]
|
Description: [Description]
|
||||||
Action: [Action]
|
Action: [Action]
|
||||||
@ -35,7 +39,7 @@ Feature: dc / intentions / update: Intention Update
|
|||||||
| Desc | allow |
|
| Desc | allow |
|
||||||
------------------------------
|
------------------------------
|
||||||
Scenario: There was an error saving the intention
|
Scenario: There was an error saving the intention
|
||||||
Given the url "/v1/connect/intentions/intention-id" responds with a 500 status
|
Given the url "/v1/connect/intentions/exact?source=default%2Fweb&destination=default%2Fdb&dc=datacenter" responds with a 500 status
|
||||||
And I submit
|
And I submit
|
||||||
Then the url should be /datacenter/intentions/intention-id
|
Then the url should be /datacenter/intentions/intention-id
|
||||||
Then "[data-notification]" has the "notification-update" class
|
Then "[data-notification]" has the "notification-update" class
|
||||||
|
@ -15,6 +15,11 @@ Feature: dc / services / show / intentions: Intentions per service
|
|||||||
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0
|
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0
|
||||||
Action: allow
|
Action: allow
|
||||||
Meta: ~
|
Meta: ~
|
||||||
|
SourceNS: default
|
||||||
|
SourceName: name
|
||||||
|
DestinationNS: default
|
||||||
|
DestinationName: destination
|
||||||
|
|
||||||
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f1
|
- ID: 755b72bd-f5ab-4c92-90cc-bed0e7d8e9f1
|
||||||
Action: deny
|
Action: deny
|
||||||
Meta: ~
|
Meta: ~
|
||||||
@ -37,6 +42,6 @@ Feature: dc / services / show / intentions: Intentions per service
|
|||||||
And I click actions on the intentions
|
And I click actions on the intentions
|
||||||
And I click delete on the intentions
|
And I click delete on the intentions
|
||||||
And I click confirmDelete on the intentions
|
And I click confirmDelete on the intentions
|
||||||
Then a DELETE request was made to "/v1/connect/intentions/755b72bd-f5ab-4c92-90cc-bed0e7d8e9f0?dc=dc1"
|
Then a DELETE request was made to "/v1/connect/intentions/exact?source=default%2Fname&destination=default%2Fdestination&dc=dc1"
|
||||||
And "[data-notification]" has the "notification-delete" class
|
And "[data-notification]" has the "notification-delete" class
|
||||||
And "[data-notification]" has the "success" class
|
And "[data-notification]" has the "success" class
|
||||||
|
@ -3,7 +3,6 @@ import { setupTest } from 'ember-qunit';
|
|||||||
module('Integration | Adapter | intention', function(hooks) {
|
module('Integration | Adapter | intention', function(hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
const dc = 'dc-1';
|
const dc = 'dc-1';
|
||||||
const legacyId = 'intention-name';
|
|
||||||
const id = 'SourceNS:SourceName:DestinationNS:DestinationName';
|
const id = 'SourceNS:SourceName:DestinationNS:DestinationName';
|
||||||
test('requestForQuery returns the correct url', function(assert) {
|
test('requestForQuery returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
@ -38,14 +37,17 @@ module('Integration | Adapter | intention', function(hooks) {
|
|||||||
test('requestForCreateRecord returns the correct url', function(assert) {
|
test('requestForCreateRecord returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `POST /v1/connect/intentions?dc=${dc}`;
|
const expected = `PUT /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`;
|
||||||
const actual = adapter
|
const actual = adapter
|
||||||
.requestForCreateRecord(
|
.requestForCreateRecord(
|
||||||
client.url,
|
client.url,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
Datacenter: dc,
|
Datacenter: dc,
|
||||||
ID: id,
|
SourceNS: 'SourceNS',
|
||||||
|
SourceName: 'SourceName',
|
||||||
|
DestinationNS: 'DestinationNS',
|
||||||
|
DestinationName: 'DestinationName',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.split('\n')[0];
|
.split('\n')[0];
|
||||||
@ -54,15 +56,17 @@ module('Integration | Adapter | intention', function(hooks) {
|
|||||||
test('requestForUpdateRecord returns the correct url', function(assert) {
|
test('requestForUpdateRecord returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `PUT /v1/connect/intentions/${legacyId}?dc=${dc}`;
|
const expected = `PUT /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`;
|
||||||
const actual = adapter
|
const actual = adapter
|
||||||
.requestForUpdateRecord(
|
.requestForUpdateRecord(
|
||||||
client.url,
|
client.url,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
Datacenter: dc,
|
Datacenter: dc,
|
||||||
ID: id,
|
SourceNS: 'SourceNS',
|
||||||
LegacyID: legacyId,
|
SourceName: 'SourceName',
|
||||||
|
DestinationNS: 'DestinationNS',
|
||||||
|
DestinationName: 'DestinationName',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.split('\n')[0];
|
.split('\n')[0];
|
||||||
@ -71,15 +75,17 @@ module('Integration | Adapter | intention', function(hooks) {
|
|||||||
test('requestForDeleteRecord returns the correct url', function(assert) {
|
test('requestForDeleteRecord returns the correct url', function(assert) {
|
||||||
const adapter = this.owner.lookup('adapter:intention');
|
const adapter = this.owner.lookup('adapter:intention');
|
||||||
const client = this.owner.lookup('service:client/http');
|
const client = this.owner.lookup('service:client/http');
|
||||||
const expected = `DELETE /v1/connect/intentions/${legacyId}?dc=${dc}`;
|
const expected = `DELETE /v1/connect/intentions/exact?source=SourceNS%2FSourceName&destination=DestinationNS%2FDestinationName&dc=${dc}`;
|
||||||
const actual = adapter
|
const actual = adapter
|
||||||
.requestForDeleteRecord(
|
.requestForDeleteRecord(
|
||||||
client.url,
|
client.url,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
Datacenter: dc,
|
Datacenter: dc,
|
||||||
ID: id,
|
SourceNS: 'SourceNS',
|
||||||
LegacyID: legacyId,
|
SourceName: 'SourceName',
|
||||||
|
DestinationNS: 'DestinationNS',
|
||||||
|
DestinationName: 'DestinationName',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.split('\n')[0];
|
.split('\n')[0];
|
||||||
|
100
ui-v2/yarn.lock
100
ui-v2/yarn.lock
@ -1487,6 +1487,14 @@
|
|||||||
"@glimmer/env" "^0.1.7"
|
"@glimmer/env" "^0.1.7"
|
||||||
"@glimmer/validator" "^0.44.0"
|
"@glimmer/validator" "^0.44.0"
|
||||||
|
|
||||||
|
"@glimmer/tracking@^1.0.1":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@glimmer/tracking/-/tracking-1.0.2.tgz#4fe9ca89e3f4a2ae8e37c8bd8e4ea0d886d9abbf"
|
||||||
|
integrity sha512-9Vp04TM2IDTShGFdxccfvnmcaj4NwqLrwbOXm4iju5KL/WkeB8mqoCSLtO3kUg+80DqU0pKE8tR460lQP8CutA==
|
||||||
|
dependencies:
|
||||||
|
"@glimmer/env" "^0.1.7"
|
||||||
|
"@glimmer/validator" "^0.44.0"
|
||||||
|
|
||||||
"@glimmer/util@^0.44.0":
|
"@glimmer/util@^0.44.0":
|
||||||
version "0.44.0"
|
version "0.44.0"
|
||||||
resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.44.0.tgz#45df98d73812440206ae7bda87cfe04aaae21ed9"
|
resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.44.0.tgz#45df98d73812440206ae7bda87cfe04aaae21ed9"
|
||||||
@ -1520,14 +1528,14 @@
|
|||||||
js-yaml "^3.13.1"
|
js-yaml "^3.13.1"
|
||||||
|
|
||||||
"@hashicorp/consul-api-double@^5.0.0":
|
"@hashicorp/consul-api-double@^5.0.0":
|
||||||
version "5.0.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.0.1.tgz#07880706ab26cc242332cef86b2c03b3b4ec4e56"
|
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.2.1.tgz#a411139fa1afa0dfaf1b9973f21275530a39939b"
|
||||||
integrity sha512-uptXq/XTGL5uzGqvwRqC0tzHKCJMVAaRMucPxjbMb4r9wOmOdT4Z2BUJD8GDcCSFIWE8hbWeqAlCXRrokZ3wbw==
|
integrity sha512-ASQv2I8iprnFmpAvbHEoKE8MXTpOxdeBan6nkgobmz4OyvMcqu/h29CGEXZ9j63NX6+nxmE84nV5yAqADRubGQ==
|
||||||
|
|
||||||
"@hashicorp/ember-cli-api-double@^3.1.0":
|
"@hashicorp/ember-cli-api-double@^3.1.0":
|
||||||
version "3.1.1"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-3.1.1.tgz#ba16a514131ce409054d1ae1a71483941d937d37"
|
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-3.1.2.tgz#0eaee116da1431ed63e55eea9ff9c28028cf9f8c"
|
||||||
integrity sha512-VLvV/m+Sx+vG+tHK1FeVjiBXwt8KcIWqgFavglrEBTkVTA2o7uP0xN9nKOJjos49KK+h1K3fCwMK5ltz7Kt97w==
|
integrity sha512-4j4JxIHBeo5KjTfcEAIrzjtiWBTxPzTTBMiigNgKAIWAtO6Hz58LQ6kDJl8MN52kSq2uSBlFJpp6aQhfwJaPtw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hashicorp/api-double" "^1.6.1"
|
"@hashicorp/api-double" "^1.6.1"
|
||||||
array-range "^1.0.1"
|
array-range "^1.0.1"
|
||||||
@ -2632,6 +2640,11 @@ babel-plugin-htmlbars-inline-precompile@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.1.0.tgz#85085b50385277f2b331ebd54e22fa91aadc24e8"
|
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.1.0.tgz#85085b50385277f2b331ebd54e22fa91aadc24e8"
|
||||||
integrity sha512-ar6c4YVX6OV7Dzpq7xRyllQrHwVEzJf41qysYULnD6tu6TS+y1FxT2VcEvMC6b9Rq9xoHMzvB79HO3av89JCGg==
|
integrity sha512-ar6c4YVX6OV7Dzpq7xRyllQrHwVEzJf41qysYULnD6tu6TS+y1FxT2VcEvMC6b9Rq9xoHMzvB79HO3av89JCGg==
|
||||||
|
|
||||||
|
babel-plugin-htmlbars-inline-precompile@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.2.0.tgz#c4882ea875d0f5683f0d91c1f72e29a4f14b5606"
|
||||||
|
integrity sha512-IUeZmgs9tMUGXYu1vfke5I18yYJFldFGdNFQOWslXTnDWXzpwPih7QFduUqvT+awDpDuNtXpdt5JAf43Q1Hhzg==
|
||||||
|
|
||||||
babel-plugin-htmlbars-inline-precompile@^4.1.0:
|
babel-plugin-htmlbars-inline-precompile@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-4.1.0.tgz#11796422e65d900a968481fa3fb37e0425c928dd"
|
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-4.1.0.tgz#11796422e65d900a968481fa3fb37e0425c928dd"
|
||||||
@ -4151,9 +4164,9 @@ caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.300010
|
|||||||
integrity sha512-PFTw9UyVfbkcMEFs82q8XVlRayj7HKvnhu5BLcmjGpv+SNyiWasCcWXPGJuO0rK0dhLRDJmtZcJ+LHUfypbw1w==
|
integrity sha512-PFTw9UyVfbkcMEFs82q8XVlRayj7HKvnhu5BLcmjGpv+SNyiWasCcWXPGJuO0rK0dhLRDJmtZcJ+LHUfypbw1w==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30000844:
|
caniuse-lite@^1.0.30000844:
|
||||||
version "1.0.30001125"
|
version "1.0.30001137"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001125.tgz#2a1a51ee045a0a2207474b086f628c34725e997b"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001137.tgz#6f0127b1d3788742561a25af3607a17fc778b803"
|
||||||
integrity sha512-9f+r7BW8Qli917mU3j0fUaTweT3f3vnX/Lcs+1C73V+RADmFme+Ih0Br8vONQi3X0lseOe6ZHfsZLCA8MSjxUA==
|
integrity sha512-54xKQZTqZrKVHmVz0+UvdZR6kQc7pJDgfhsMYDG19ID1BWoNnDMFm5Q3uSBSU401pBvKYMsHAt9qhEDcxmk8aw==
|
||||||
|
|
||||||
capture-exit@^2.0.0:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -5204,9 +5217,9 @@ electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.488:
|
|||||||
integrity sha512-EOZuaDT3L1sCIMAVN5J0nGuGWVq5dThrdl0d8XeDYf4MOzbXqZ19OLKesN8TZj0RxtpYjqHpiw/fR6BKWdMwYA==
|
integrity sha512-EOZuaDT3L1sCIMAVN5J0nGuGWVq5dThrdl0d8XeDYf4MOzbXqZ19OLKesN8TZj0RxtpYjqHpiw/fR6BKWdMwYA==
|
||||||
|
|
||||||
electron-to-chromium@^1.3.47:
|
electron-to-chromium@^1.3.47:
|
||||||
version "1.3.565"
|
version "1.3.575"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.565.tgz#8511797ab2b66b767e1aef4eb17d636bf01a2c72"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.575.tgz#57065cfad7b977a817dba47b28e7eb4dbce3fc37"
|
||||||
integrity sha512-me5dGlHFd8Q7mKhqbWRLIYnKjw4i0fO6hmW0JBxa7tM87fBfNEjWokRnDF7V+Qme/9IYpwhfMn+soWs40tXWqg==
|
integrity sha512-031VrjcilnE8bXivDGhEeuGjMZrjTAeyAKm3XWPY9SvGYE6Hn8003gCqoNszFu6lh1v0gDx5hrM0VE1cPSMUkQ==
|
||||||
|
|
||||||
elliptic@^6.0.0, elliptic@^6.5.2:
|
elliptic@^6.0.0, elliptic@^6.5.2:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
@ -5286,26 +5299,33 @@ ember-basic-dropdown@^3.0.3:
|
|||||||
ember-maybe-in-element "^0.4.0"
|
ember-maybe-in-element "^0.4.0"
|
||||||
ember-truth-helpers "2.1.0"
|
ember-truth-helpers "2.1.0"
|
||||||
|
|
||||||
ember-changeset-validations@^3.0.2:
|
ember-changeset-conditional-validations@^0.6.0:
|
||||||
version "3.7.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-changeset-validations/-/ember-changeset-validations-3.7.0.tgz#74875705128f56ffe22bf54fe6856838dc9caa7d"
|
resolved "https://registry.yarnpkg.com/ember-changeset-conditional-validations/-/ember-changeset-conditional-validations-0.6.0.tgz#78369ad3af0aea338e00a9bdf1b622fb512d9a00"
|
||||||
integrity sha512-E4Um4IV5UO72FdT1GWcI8EbNOHE7eS16XFokk6UaKpXs3MLuI1Ev6Zb8Q9Z20g/9/IieBI4XddnGd+1FpXz6aA==
|
integrity sha512-U9TZFhhLC+5XRqcI5sNfJwGVcVZvXJxwrRQrrTYLImHe/+tcgP/TagE0f0DBrgWV2u3lsztGHGwGUs86uc65rg==
|
||||||
dependencies:
|
dependencies:
|
||||||
ember-changeset "^3.7.0"
|
ember-cli-babel "^6.16.0"
|
||||||
|
|
||||||
|
ember-changeset-validations@^3.9.0:
|
||||||
|
version "3.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ember-changeset-validations/-/ember-changeset-validations-3.9.1.tgz#5f44f56ca9a55ac079f667f8cbead1dfeb85a21d"
|
||||||
|
integrity sha512-aTufViqh4zx8WNjiuxuQNSfYaDLDdXl7mQ6g18z2Wma55kEmGymxaltM3lrGgXK+IlWoJvINrNBd6i6AYRxaYA==
|
||||||
|
dependencies:
|
||||||
|
ember-changeset "^3.9.1"
|
||||||
ember-cli-babel "^7.8.0"
|
ember-cli-babel "^7.8.0"
|
||||||
ember-cli-htmlbars "^4.0.5"
|
ember-cli-htmlbars "^4.0.5"
|
||||||
ember-get-config "^0.2.4"
|
ember-get-config "^0.2.4"
|
||||||
ember-validators "^2.0.0"
|
ember-validators "^2.0.0"
|
||||||
|
|
||||||
ember-changeset@^3.7.0:
|
ember-changeset@^3.9.1:
|
||||||
version "3.7.1"
|
version "3.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/ember-changeset/-/ember-changeset-3.7.1.tgz#5826e2bb7151f85494208aedac25a00400bddf13"
|
resolved "https://registry.yarnpkg.com/ember-changeset/-/ember-changeset-3.9.1.tgz#53b50be95364a71f38e68a01c33eba12808cd8a4"
|
||||||
integrity sha512-vYkF9LHoFwQJb9yytfbA9J888a82/4i+NQUeYtyuLtoD4Zty6Z1NzZ5eWwVU/RbsbK45x85T5FKmsxSS1/1cgw==
|
integrity sha512-Ntf0fITb2klRZF+5s5xbBQ6HNuSn1IbwppyZPU8v7oh26QOlfjyxYE7QNWM3nZnybHkCrF1iSqN3s8xj3OoFCg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@glimmer/tracking" "^1.0.0"
|
"@glimmer/tracking" "^1.0.1"
|
||||||
ember-auto-import "^1.5.2"
|
ember-auto-import "^1.5.2"
|
||||||
ember-cli-babel "^7.19.0"
|
ember-cli-babel "^7.19.0"
|
||||||
validated-changeset "~0.7.1"
|
validated-changeset "~0.9.1"
|
||||||
|
|
||||||
ember-cli-app-version@^3.2.0:
|
ember-cli-app-version@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
@ -5328,7 +5348,7 @@ ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
|
resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
|
||||||
integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
|
integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
|
||||||
|
|
||||||
ember-cli-babel@7:
|
ember-cli-babel@7, ember-cli-babel@^7.8.0:
|
||||||
version "7.22.1"
|
version "7.22.1"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.22.1.tgz#cad28b89cf0e184c93b863d09bc5ba4ce1d2e453"
|
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.22.1.tgz#cad28b89cf0e184c93b863d09bc5ba4ce1d2e453"
|
||||||
integrity sha512-kCT8WbC1AYFtyOpU23ESm22a+gL6fWv8Nzwe8QFQ5u0piJzM9MEudfbjADEaoyKTrjMQTDsrWwEf3yjggDsOng==
|
integrity sha512-kCT8WbC1AYFtyOpU23ESm22a+gL6fWv8Nzwe8QFQ5u0piJzM9MEudfbjADEaoyKTrjMQTDsrWwEf3yjggDsOng==
|
||||||
@ -5379,7 +5399,7 @@ ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0,
|
|||||||
ember-cli-version-checker "^2.1.2"
|
ember-cli-version-checker "^2.1.2"
|
||||||
semver "^5.5.0"
|
semver "^5.5.0"
|
||||||
|
|
||||||
ember-cli-babel@^7.1.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.1.3, ember-cli-babel@^7.10.0, ember-cli-babel@^7.11.0, ember-cli-babel@^7.11.1, ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.19.0, ember-cli-babel@^7.20.0, ember-cli-babel@^7.20.5, ember-cli-babel@^7.21.0, ember-cli-babel@^7.7.3, ember-cli-babel@^7.8.0:
|
ember-cli-babel@^7.1.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.1.3, ember-cli-babel@^7.10.0, ember-cli-babel@^7.11.0, ember-cli-babel@^7.11.1, ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.19.0, ember-cli-babel@^7.20.0, ember-cli-babel@^7.20.5, ember-cli-babel@^7.21.0, ember-cli-babel@^7.7.3:
|
||||||
version "7.21.0"
|
version "7.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc"
|
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.21.0.tgz#c79e888876aee87dfc3260aee7cb580b74264bbc"
|
||||||
integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw==
|
integrity sha512-jHVi9melAibo0DrAG3GAxid+29xEyjBoU53652B4qcu3Xp58feZGTH/JGXovH7TjvbeNn65zgNyoV3bk1onULw==
|
||||||
@ -5505,7 +5525,27 @@ ember-cli-htmlbars@^3.0.0, ember-cli-htmlbars@^3.0.1:
|
|||||||
json-stable-stringify "^1.0.1"
|
json-stable-stringify "^1.0.1"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
ember-cli-htmlbars@^4.0.5, ember-cli-htmlbars@^4.2.0, ember-cli-htmlbars@^4.2.2, ember-cli-htmlbars@^4.3.1:
|
ember-cli-htmlbars@^4.0.5:
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.4.0.tgz#7ca17d5ca8f7550984346d9e6e93da0c3323f8d9"
|
||||||
|
integrity sha512-ohgctqk7dXIZR4TgN0xRoUYltWhghFJgqmtuswQTpZ7p74RxI9PKx+E8WV/95mGcPzraesvMNBg5utQNvcqgNg==
|
||||||
|
dependencies:
|
||||||
|
"@ember/edition-utils" "^1.2.0"
|
||||||
|
babel-plugin-htmlbars-inline-precompile "^3.2.0"
|
||||||
|
broccoli-debug "^0.6.5"
|
||||||
|
broccoli-persistent-filter "^2.3.1"
|
||||||
|
broccoli-plugin "^3.1.0"
|
||||||
|
common-tags "^1.8.0"
|
||||||
|
ember-cli-babel-plugin-helpers "^1.1.0"
|
||||||
|
fs-tree-diff "^2.0.1"
|
||||||
|
hash-for-dep "^1.5.1"
|
||||||
|
heimdalljs-logger "^0.1.10"
|
||||||
|
json-stable-stringify "^1.0.1"
|
||||||
|
semver "^6.3.0"
|
||||||
|
strip-bom "^4.0.0"
|
||||||
|
walk-sync "^2.0.2"
|
||||||
|
|
||||||
|
ember-cli-htmlbars@^4.2.0, ember-cli-htmlbars@^4.2.2, ember-cli-htmlbars@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.3.1.tgz#4af8adc21ab3c4953f768956b7f7d207782cb175"
|
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-4.3.1.tgz#4af8adc21ab3c4953f768956b7f7d207782cb175"
|
||||||
integrity sha512-CW6AY/yzjeVqoRtItOKj3hcYzc5dWPRETmeCzr2Iqjt5vxiVtpl0z5VTqHqIlT5fsFx6sGWBQXNHIe+ivYsxXQ==
|
integrity sha512-CW6AY/yzjeVqoRtItOKj3hcYzc5dWPRETmeCzr2Iqjt5vxiVtpl0z5VTqHqIlT5fsFx6sGWBQXNHIe+ivYsxXQ==
|
||||||
@ -12957,10 +12997,10 @@ validate-npm-package-name@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
builtins "^1.0.3"
|
builtins "^1.0.3"
|
||||||
|
|
||||||
validated-changeset@~0.7.1:
|
validated-changeset@~0.9.1:
|
||||||
version "0.7.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/validated-changeset/-/validated-changeset-0.7.1.tgz#cb2c11c93d5acfb2286e5bfca07f4a516f83c844"
|
resolved "https://registry.yarnpkg.com/validated-changeset/-/validated-changeset-0.9.1.tgz#bb4773c7c5392dcc7ecfbc8ccc10c7fef2840d42"
|
||||||
integrity sha512-BbFK98Cp7WunEwLOW/oAi6qDZZFBOLkae0q5RZ3ne8ZkwB1sskOYfF5IqQhqubwxRb4emMhA2UgN5rdfOaZxXQ==
|
integrity sha512-gEMvF+GN8ECLndHw5Ehc9ckXMgM+RRDK5+lCx2hGX9Qie1q68ixLEtbNXbPPhV4JHq6d7krVfTG+sEQ3X6zoHA==
|
||||||
|
|
||||||
vary@~1.1.2:
|
vary@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user