mirror of https://github.com/status-im/consul.git
WIP: First draft intentions
1. Listing, filtering by action and searching by source name and destination name 2. Edit/Create page, edits ping the API double fine, need to work through creates and deletes 3. Currently uses a `Precedence` intention keyname that doesn't yet exist in the real API
This commit is contained in:
parent
c3e92a236f
commit
b38e5df630
|
@ -0,0 +1,64 @@
|
|||
import Adapter, { DATACENTER_KEY as API_DATACENTER_KEY } from './application';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/intention';
|
||||
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
|
||||
import makeAttrable from 'consul-ui/utils/makeAttrable';
|
||||
export default Adapter.extend({
|
||||
urlForQuery: function(query, modelName) {
|
||||
return this.appendURL('connect/intentions', [], this.cleanQuery(query));
|
||||
},
|
||||
urlForQueryRecord: function(query, modelName) {
|
||||
return this.appendURL('connect/intentions', [query.id], this.cleanQuery(query));
|
||||
},
|
||||
urlForCreateRecord: function(modelName, snapshot) {
|
||||
return this.appendURL('connect/intentions', [], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForUpdateRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('connect/intentions', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
urlForDeleteRecord: function(id, modelName, snapshot) {
|
||||
return this.appendURL('connect/intentions', [snapshot.attr(SLUG_KEY)], {
|
||||
[API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY),
|
||||
});
|
||||
},
|
||||
isUpdateRecord: function(url) {
|
||||
return (
|
||||
url.pathname ===
|
||||
this.parseURL(
|
||||
this.urlForUpdateRecord(null, 'intention', makeAttrable({ [DATACENTER_KEY]: '' }))
|
||||
).pathname
|
||||
);
|
||||
},
|
||||
handleResponse: function(status, headers, payload, requestData) {
|
||||
let response = payload;
|
||||
if (status === HTTP_OK) {
|
||||
const url = this.parseURL(requestData.url);
|
||||
switch (true) {
|
||||
case this.isQueryRecord(url):
|
||||
case this.isUpdateRecord(url):
|
||||
// case this.isCreateRecord(url):
|
||||
response = {
|
||||
...response,
|
||||
...{
|
||||
[PRIMARY_KEY]: this.uidForURL(url),
|
||||
},
|
||||
};
|
||||
break;
|
||||
default:
|
||||
response = response.map((item, i, arr) => {
|
||||
return {
|
||||
...item,
|
||||
...{
|
||||
[PRIMARY_KEY]: this.uidForURL(url, item[SLUG_KEY]),
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
return this._super(status, headers, response, requestData);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: 'form',
|
||||
classNames: ['filter-bar'],
|
||||
'data-test-intention-filter': true,
|
||||
onchange: function() {},
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
import Controller from './edit';
|
||||
export default Controller.extend();
|
|
@ -0,0 +1,30 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { set } from '@ember/object';
|
||||
// import Changeset from 'ember-changeset';
|
||||
// import validations from 'consul-ui/validations/acl';
|
||||
// import lookupValidator from 'ember-changeset-validations';
|
||||
|
||||
export default Controller.extend({
|
||||
setProperties: function(model) {
|
||||
this.changeset = model.item; //new Changeset(model.item, lookupValidator(validations), validations);
|
||||
this._super({
|
||||
...model,
|
||||
...{
|
||||
item: this.changeset,
|
||||
},
|
||||
});
|
||||
},
|
||||
actions: {
|
||||
change: function(e) {
|
||||
const target = e.target;
|
||||
switch (target.name) {
|
||||
case 'SourceType':
|
||||
set(this.changeset, target.name, target.value);
|
||||
break;
|
||||
case 'Action':
|
||||
set(this.changeset, target.name, target.value);
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { computed, get } from '@ember/object';
|
||||
import WithFiltering from 'consul-ui/mixins/with-filtering';
|
||||
import ucfirst from 'consul-ui/utils/ucfirst';
|
||||
import numeral from 'numeral';
|
||||
// TODO: DRY out in acls at least
|
||||
const createCounter = function(prop) {
|
||||
return function(items, val) {
|
||||
return val === '' ? get(items, 'length') : items.filterBy(prop, val).length;
|
||||
};
|
||||
};
|
||||
const countAction = createCounter('Action');
|
||||
export default Controller.extend(WithFiltering, {
|
||||
queryParams: {
|
||||
action: {
|
||||
as: 'action',
|
||||
},
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
actionFilters: computed('items', function() {
|
||||
const items = get(this, 'items');
|
||||
return ['', 'allow', 'deny'].map(function(item) {
|
||||
return {
|
||||
label: `${item === '' ? 'All' : ucfirst(item)} (${numeral(
|
||||
countAction(items, item)
|
||||
).format()})`,
|
||||
value: item,
|
||||
};
|
||||
});
|
||||
}),
|
||||
filter: function(item, { s = '', action = '' }) {
|
||||
return (
|
||||
(get(item, 'SourceName')
|
||||
.toLowerCase()
|
||||
.indexOf(s.toLowerCase()) !== -1 ||
|
||||
get(item, 'DestinationName')
|
||||
.toLowerCase()
|
||||
.indexOf(s.toLowerCase()) !== -1) &&
|
||||
(action === '' || get(item, 'Action') === action)
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import { get } from '@ember/object';
|
||||
import WithFeedback from 'consul-ui/mixins/with-feedback';
|
||||
|
||||
export default Mixin.create(WithFeedback, {
|
||||
actions: {
|
||||
create: function(item) {
|
||||
get(this, 'feedback').execute(
|
||||
() => {
|
||||
return get(this, 'repo')
|
||||
.persist(item)
|
||||
.then(item => {
|
||||
return this.transitionTo('dc.intentions');
|
||||
});
|
||||
},
|
||||
`Your intention has been added.`,
|
||||
`There was an error adding your intention.`
|
||||
);
|
||||
},
|
||||
update: function(item) {
|
||||
get(this, 'feedback').execute(
|
||||
() => {
|
||||
return get(this, 'repo')
|
||||
.persist(item)
|
||||
.then(() => {
|
||||
return this.transitionTo('dc.intentions');
|
||||
});
|
||||
},
|
||||
`Your intention was saved.`,
|
||||
`There was an error saving your intention.`
|
||||
);
|
||||
},
|
||||
delete: function(item) {
|
||||
get(this, 'feedback').execute(
|
||||
() => {
|
||||
return (
|
||||
get(this, 'repo')
|
||||
// ember-changeset doesn't support `get`
|
||||
// and `data` returns an object not a model
|
||||
.remove(item)
|
||||
.then(() => {
|
||||
switch (this.routeName) {
|
||||
case 'dc.intentions.index':
|
||||
return this.refresh();
|
||||
default:
|
||||
return this.transitionTo('dc.intentions');
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
`Your intention was deleted.`,
|
||||
`There was an error deleting your intention.`
|
||||
);
|
||||
},
|
||||
cancel: function(item) {
|
||||
this.transitionTo('dc.intentions');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
Description: attr('string'),
|
||||
SourceNS: attr('string'),
|
||||
SourceName: attr('string'),
|
||||
DestinationName: attr('string'),
|
||||
Precedence: attr('number'),
|
||||
SourceType: attr('string'),
|
||||
Action: attr('string'),
|
||||
DefaultAddr: attr('string'),
|
||||
DefaultPort: attr('number'),
|
||||
Meta: attr(),
|
||||
Datacenter: attr('string'),
|
||||
CreatedAt: attr('date'),
|
||||
UpdatedAt: attr('date'),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
|
@ -19,6 +19,11 @@ Router.map(function() {
|
|||
// Show an individual node
|
||||
this.route('show', { path: '/:name' });
|
||||
});
|
||||
// Intentions represent a consul intention
|
||||
this.route('intentions', { path: '/intentions' }, function() {
|
||||
this.route('edit', { path: '/:id' });
|
||||
this.route('create', { path: '/create' });
|
||||
});
|
||||
// Key/Value
|
||||
this.route('kv', { path: '/kv' }, function() {
|
||||
this.route('folder', { path: '/*key' });
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get, set } from '@ember/object';
|
||||
import WithIntentionActions from 'consul-ui/mixins/intention/with-actions';
|
||||
|
||||
export default Route.extend(WithIntentionActions, {
|
||||
templateName: 'dc/intentions/edit',
|
||||
repo: service('intentions'),
|
||||
beforeModel: function() {
|
||||
get(this, 'repo').invalidate();
|
||||
},
|
||||
model: function(params) {
|
||||
this.item = get(this, 'repo').create();
|
||||
set(this.item, 'Datacenter', this.modelFor('dc').dc.Name);
|
||||
return hash({
|
||||
create: true,
|
||||
isLoading: false,
|
||||
item: this.item,
|
||||
types: ['consul', 'externaluri'],
|
||||
intents: ['allow', 'deny'],
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
deactivate: function() {
|
||||
if (get(this.item, 'isNew')) {
|
||||
this.item.destroyRecord();
|
||||
}
|
||||
},
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/intention/with-actions';
|
||||
|
||||
export default Route.extend(WithAclActions, {
|
||||
repo: service('intentions'),
|
||||
model: function(params) {
|
||||
return hash({
|
||||
isLoading: false,
|
||||
item: get(this, 'repo').findBySlug(params.id, this.modelFor('dc').dc.Name),
|
||||
types: ['consul', 'externaluri'],
|
||||
intents: ['allow', 'deny'],
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Route.extend({
|
||||
repo: service('intentions'),
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
return hash({
|
||||
items: get(this, 'repo').findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import Serializer from './application';
|
||||
import { PRIMARY_KEY } from 'consul-ui/models/intention';
|
||||
|
||||
export default Serializer.extend({
|
||||
primaryKey: PRIMARY_KEY,
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import Service, { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { typeOf } from '@ember/utils';
|
||||
import { PRIMARY_KEY } from 'consul-ui/models/intention';
|
||||
export default Service.extend({
|
||||
store: service('store'),
|
||||
findAllByDatacenter: function(dc) {
|
||||
return get(this, 'store')
|
||||
.query('intention', { dc: dc })
|
||||
.then(function(items) {
|
||||
return items.forEach(function(item, i, arr) {
|
||||
set(item, 'Datacenter', dc);
|
||||
});
|
||||
});
|
||||
},
|
||||
findBySlug: function(slug, dc) {
|
||||
return get(this, 'store')
|
||||
.queryRecord('intention', {
|
||||
id: slug,
|
||||
dc: dc,
|
||||
})
|
||||
.then(function(item) {
|
||||
set(item, 'Datacenter', dc);
|
||||
return item;
|
||||
});
|
||||
},
|
||||
create: function() {
|
||||
return get(this, 'store').createRecord('intention');
|
||||
},
|
||||
persist: function(item) {
|
||||
return item.save();
|
||||
},
|
||||
remove: function(obj) {
|
||||
let item = obj;
|
||||
if (typeof obj.destroyRecord === 'undefined') {
|
||||
item = obj.get('data');
|
||||
}
|
||||
if (typeOf(item) === 'object') {
|
||||
item = get(this, 'store').peekRecord('intention', item[PRIMARY_KEY]);
|
||||
}
|
||||
return item.destroyRecord().then(item => {
|
||||
return get(this, 'store').unloadRecord(item);
|
||||
});
|
||||
},
|
||||
invalidate: function() {
|
||||
return get(this, 'store').unloadAll('intention');
|
||||
},
|
||||
});
|
|
@ -45,6 +45,7 @@
|
|||
@import 'components/notice';
|
||||
|
||||
@import 'routes/dc/service/index';
|
||||
@import 'routes/dc/intention/index';
|
||||
@import 'routes/dc/kv/index';
|
||||
|
||||
main a {
|
||||
|
|
|
@ -25,12 +25,13 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
%with-folder {
|
||||
position: relative;
|
||||
text-indent: 30px;
|
||||
}
|
||||
%with-hashicorp,
|
||||
%with-folder,
|
||||
%with-chevron,
|
||||
%with-clipboard {
|
||||
%with-clipboard,
|
||||
%with-right-arrow {
|
||||
position: relative;
|
||||
}
|
||||
%with-chevron {
|
||||
|
@ -142,6 +143,26 @@
|
|||
@extend %pseudo-icon;
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="14" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M13.645 10.092c.24.409.365.88.365 1.37 0 1.392-1.027 2.527-2.294 2.538H2.322c-.824 0-1.592-.487-2.004-1.27a2.761 2.761 0 0 1 0-2.538l4.686-8.904C5.416.505 6.184.018 7.008.018c.824 0 1.592.487 2.004 1.27l4.633 8.804zm-5.989 1.264V9.607H6.344v1.749h1.312zm0-3.048v-4.37H6.344v4.37h1.312z" fill="%23949daa"/></svg>');
|
||||
}
|
||||
%with-right-arrow-green {
|
||||
@extend %pseudo-icon;
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M9.263.5L8.084 1.637l4.716 4.55H0v1.625h12.8l-4.716 4.55 1.18 1.138L16 7z" fill="%232EB039"/></svg>');
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
background-color: transparent;
|
||||
}
|
||||
%with-deny-icon {
|
||||
@extend %pseudo-icon;
|
||||
background-image: url('data:image/svg+xml;charset=UTF-8,<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#282C2E" d="M8.79 4l-.737.71L11 7.556H3V8.57h8l-2.947 2.844.736.711L13 8.062z"/><rect stroke="#C73445" stroke-width="1.5" x=".75" y=".75" width="14.5" height="14.5" rx="7.25"/><path d="M3.5 3.5l9 9" stroke="#C73445" stroke-width="1.5" stroke-linecap="square"/></g></svg>');
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: transparent;
|
||||
}
|
||||
%with-deny::before {
|
||||
@extend %with-deny-icon;
|
||||
}
|
||||
%with-allow::before {
|
||||
@extend %with-right-arrow-green;
|
||||
}
|
||||
%with-passing::before {
|
||||
@extend %with-tick;
|
||||
border-radius: 100%;
|
||||
|
|
|
@ -19,6 +19,9 @@ table tr > * {
|
|||
html.template-service.template-list main table tr {
|
||||
@extend %services-row;
|
||||
}
|
||||
html.template-intention.template-list main table tr {
|
||||
@extend %intentions-row;
|
||||
}
|
||||
html.template-kv.template-list main table tr {
|
||||
@extend %kvs-row;
|
||||
}
|
||||
|
@ -65,6 +68,12 @@ html.template-node.template-show main table.sessions tr {
|
|||
tr > * dl {
|
||||
float: left;
|
||||
}
|
||||
%intentions-row > * {
|
||||
width: calc(25% - 60px);
|
||||
}
|
||||
%intentions-row > *:last-child {
|
||||
width: 60px;
|
||||
}
|
||||
%kvs-row > *:first-child {
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
td.intent-allow strong {
|
||||
@extend %with-allow;
|
||||
visibility: hidden;
|
||||
}
|
||||
td.intent-deny strong {
|
||||
@extend %with-deny;
|
||||
visibility: hidden;
|
||||
}
|
|
@ -27,6 +27,9 @@
|
|||
<li data-test-main-nav-nodes class={{if (is-href 'dc.nodes' dc.Name) 'is-active'}}>
|
||||
<a href={{href-to 'dc.nodes' dc.Name}}>Nodes</a>
|
||||
</li>
|
||||
<li data-test-main-nav-intentions class={{if (is-href 'dc.intentions' dc.Name) 'is-active'}}>
|
||||
<a href={{href-to 'dc.intentions' dc.Name}}>Intentions</a>
|
||||
</li>
|
||||
<li data-test-main-nav-kvs class={{if (is-href 'dc.kv' dc.Name) 'is-active'}}>
|
||||
<a href={{href-to 'dc.kv' dc.Name}}>Key/Value</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{{!<form>}}
|
||||
{{freetext-filter onchange=(action onchange) value=search placeholder="Search by Source or Destination"}}
|
||||
{{radio-group name="action" value=action items=filters onchange=(action onchange)}}
|
||||
{{!</form>}}
|
|
@ -0,0 +1,58 @@
|
|||
<form>
|
||||
<fieldset>
|
||||
<label class="type-text{{if item.error.SourceName ' has-error'}}">
|
||||
<span>Source Service</span>
|
||||
{{input value=item.SourceName name='source' autofocus='autofocus'}}
|
||||
<em>Choose a Consul Service, write in a future Consul Service, or write any Service URL</em>
|
||||
</label>
|
||||
<div role="radiogroup" class={{if item.error.Type ' has-error'}}>
|
||||
{{#each types as |type|}}
|
||||
<label>
|
||||
<span>{{type}}</span>
|
||||
<input type="radio" name="SourceType" value="{{type}}" checked={{if (eq item.SourceType type) 'checked'}} onchange={{ action 'change' }}/>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
<label class="type-text{{if item.error.DestinationName ' has-error'}}">
|
||||
<span>Destination Service</span>
|
||||
{{input value=item.DestinationName name='name'}}
|
||||
<em>Choose a Consul Service, write in a future Consul Service, or write any Service URL</em>
|
||||
</label>
|
||||
<div role="radiogroup" class={{if item.error.Action ' has-error'}}>
|
||||
{{#each itents as |intent|}}
|
||||
<label>
|
||||
<span>{{intent}}</span>
|
||||
<input type="radio" name="Action" value="{{intent}}" checked={{if (eq item.Action intent) 'checked'}} onchange={{ action 'change' }}/>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
<label class="type-text{{if item.error.Description ' has-error'}}">
|
||||
<span>Description</span>
|
||||
{{input value=item.Description name='description' placeholder="Description"}}
|
||||
<em>Choose a Consul Service, write in a future Consul Service, or write any Service URL</em>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div>
|
||||
{{#if create }}
|
||||
<button type="submit" {{ action "create" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
|
||||
{{ else }}
|
||||
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
|
||||
{{/if}}
|
||||
<button type="reset" {{ action "cancel" item}}>Cancel</button>
|
||||
{{# if (and item.ID (not-eq item.ID 'anonymous')) }}
|
||||
{{#confirmation-dialog message='Are you sure you want to delete this Intention?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
<button type="button" class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<button type="button" class="type-delete" {{action execute}}>Confirm Delete</button>
|
||||
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||
{{/block-slot}}
|
||||
{{/confirmation-dialog}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{{#app-view class="acl edit" loading=isLoading}}
|
||||
{{#block-slot 'breadcrumbs'}}
|
||||
<ol>
|
||||
<li><a href={{href-to 'dc.intentions'}}>All Intentions</a></li>
|
||||
</ol>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'header'}}
|
||||
<h1>
|
||||
{{#if item.ID }}
|
||||
Edit Intention
|
||||
{{else}}
|
||||
New Intention
|
||||
{{/if}}
|
||||
</h1>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'actions'}}
|
||||
{{#if (not create) }}
|
||||
{{#feedback-dialog type='inline'}}
|
||||
{{#block-slot 'action' as |success error|}}
|
||||
{{#copy-button success=(action success) error=(action error) clipboardText=item.ID title='copy UUID to clipboard'}}
|
||||
Copy UUID
|
||||
{{/copy-button}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'success'}}
|
||||
<p>
|
||||
Copied UUID!
|
||||
</p>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'error'}}
|
||||
<p>
|
||||
Sorry, something went wrong!
|
||||
</p>
|
||||
{{/block-slot}}
|
||||
{{/feedback-dialog}}
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'content'}}
|
||||
{{ partial 'dc/intentions/form'}}
|
||||
{{/block-slot}}
|
||||
{{/app-view}}
|
|
@ -0,0 +1,72 @@
|
|||
{{#app-view class="intention list"}}
|
||||
{{#block-slot 'header'}}
|
||||
<h1>
|
||||
Intentions
|
||||
</h1>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'actions'}}
|
||||
<a href="{{href-to 'dc.intentions.create'}}" class="type-create">Create</a>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'toolbar'}}
|
||||
{{#if (gt items.length 0) }}
|
||||
{{intention-filter filters=actionFilters search=filters.s type=filters.action onchange=(action 'filter')}}
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'content'}}
|
||||
{{#if (gt filtered.length 0) }}
|
||||
{{#tabular-collection
|
||||
route='dc.intentions.edit'
|
||||
key='SourceName'
|
||||
items=filtered as |item index|
|
||||
}}
|
||||
{{#block-slot 'header'}}
|
||||
<th>Source</th>
|
||||
<th> </th>
|
||||
<th>Destination</th>
|
||||
<th>Precedence</th>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'row'}}
|
||||
<td data-test-intention="{{item.ID}}">
|
||||
<a href={{href-to 'dc.intentions.edit' item.ID}}>{{item.SourceName}}</a>
|
||||
</td>
|
||||
<td class="intent-{{item.Action}}">
|
||||
<strong>{{item.Action}}</strong>
|
||||
</td>
|
||||
<td>
|
||||
{{item.DestinationName}}
|
||||
</td>
|
||||
<td>
|
||||
{{item.Precedence}}
|
||||
</td>
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'actions' as |index change checked|}}
|
||||
{{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this intention?'}}
|
||||
{{#block-slot 'action' as |confirm|}}
|
||||
{{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
|
||||
<ul>
|
||||
<li>
|
||||
<a href={{href-to 'dc.intentions.edit' item.ID}}>Edit</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onclick={{action confirm 'delete' item}}>Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{/action-group}}
|
||||
{{/block-slot}}
|
||||
{{#block-slot 'dialog' as |execute cancel message|}}
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<button type="button" class="type-delete" {{action execute}}>Confirm Delete</button>
|
||||
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||
{{/block-slot}}
|
||||
{{/confirmation-dialog}}
|
||||
{{/block-slot}}
|
||||
{{/tabular-collection}}
|
||||
{{else}}
|
||||
<p>
|
||||
There are no intentions.
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/block-slot}}
|
||||
{{/app-view}}
|
|
@ -0,0 +1,29 @@
|
|||
import { moduleForComponent, test } from 'ember-qunit';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('intention-filter', 'Integration | Component | intention filter', {
|
||||
integration: true,
|
||||
});
|
||||
|
||||
test('it renders', function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||
|
||||
this.render(hbs`{{intention-filter}}`);
|
||||
|
||||
assert.equal(
|
||||
this.$()
|
||||
.text()
|
||||
.trim(),
|
||||
'Search'
|
||||
);
|
||||
|
||||
// // Template block usage:
|
||||
// this.render(hbs`
|
||||
// {{#intention-filter}}
|
||||
// template block text
|
||||
// {{/intention-filter}}
|
||||
// `);
|
||||
|
||||
// assert.equal(this.$().text().trim(), 'template block text');
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Adapter | intention', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let adapter = this.owner.lookup('adapter:intention');
|
||||
assert.ok(adapter);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('controller:dc/intentions/create', 'Unit | Controller | dc/intentions/create', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.subject();
|
||||
assert.ok(controller);
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('controller:dc/intentions/edit', 'Unit | Controller | dc/intentions/edit', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.subject();
|
||||
assert.ok(controller);
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('controller:dc/intentions/index', 'Unit | Controller | dc/intentions/index', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['controller:foo']
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.subject();
|
||||
assert.ok(controller);
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import EmberObject from '@ember/object';
|
||||
import IntentionWithActionsMixin from 'consul-ui/mixins/intention/with-actions';
|
||||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('mixin:intention/with-actions', 'Unit | Mixin | intention/with actions', {
|
||||
// Specify the other units that are required for this test.
|
||||
needs: ['service:feedback'],
|
||||
subject: function() {
|
||||
const IntentionWithActionsObject = EmberObject.extend(IntentionWithActionsMixin);
|
||||
this.register('test-container:intention/with-actions-object', IntentionWithActionsObject);
|
||||
// TODO: May need to actually get this from the container
|
||||
return IntentionWithActionsObject;
|
||||
},
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it works', function(assert) {
|
||||
const subject = this.subject();
|
||||
assert.ok(subject);
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module('Unit | Model | intention', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = run(() => store.createRecord('intention', {}));
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('route:dc/intentions/create', 'Unit | Route | dc/intentions/create', {
|
||||
// Specify the other units that are required for this test.
|
||||
needs: ['service:intentions', 'service:feedback', 'service:flashMessages'],
|
||||
});
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.subject();
|
||||
assert.ok(route);
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('route:dc/intentions/edit', 'Unit | Route | dc/intentions/edit', {
|
||||
// Specify the other units that are required for this test.
|
||||
needs: ['service:intentions', 'service:feedback', 'service:flashMessages'],
|
||||
});
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.subject();
|
||||
assert.ok(route);
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('route:dc/intentions/index', 'Unit | Route | dc/intentions/index', {
|
||||
// Specify the other units that are required for this test.
|
||||
needs: ['service:intentions', 'service:feedback', 'service:flashMessages'],
|
||||
});
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.subject();
|
||||
assert.ok(route);
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module('Unit | Serializer | intention', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('intention');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = run(() => store.createRecord('intention', {}));
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
moduleFor('service:intentions', 'Unit | Service | intentions', {
|
||||
// Specify the other units that are required for this test.
|
||||
// needs: ['service:foo']
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.subject();
|
||||
assert.ok(service);
|
||||
});
|
Loading…
Reference in New Issue