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
|
// Show an individual node
|
||||||
this.route('show', { path: '/:name' });
|
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
|
// Key/Value
|
||||||
this.route('kv', { path: '/kv' }, function() {
|
this.route('kv', { path: '/kv' }, function() {
|
||||||
this.route('folder', { path: '/*key' });
|
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 'components/notice';
|
||||||
|
|
||||||
@import 'routes/dc/service/index';
|
@import 'routes/dc/service/index';
|
||||||
|
@import 'routes/dc/intention/index';
|
||||||
@import 'routes/dc/kv/index';
|
@import 'routes/dc/kv/index';
|
||||||
|
|
||||||
main a {
|
main a {
|
||||||
|
|
|
@ -25,12 +25,13 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
%with-folder {
|
%with-folder {
|
||||||
position: relative;
|
|
||||||
text-indent: 30px;
|
text-indent: 30px;
|
||||||
}
|
}
|
||||||
%with-hashicorp,
|
%with-hashicorp,
|
||||||
|
%with-folder,
|
||||||
%with-chevron,
|
%with-chevron,
|
||||||
%with-clipboard {
|
%with-clipboard,
|
||||||
|
%with-right-arrow {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
%with-chevron {
|
%with-chevron {
|
||||||
|
@ -142,6 +143,26 @@
|
||||||
@extend %pseudo-icon;
|
@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>');
|
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 {
|
%with-passing::before {
|
||||||
@extend %with-tick;
|
@extend %with-tick;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
|
@ -19,6 +19,9 @@ table tr > * {
|
||||||
html.template-service.template-list main table tr {
|
html.template-service.template-list main table tr {
|
||||||
@extend %services-row;
|
@extend %services-row;
|
||||||
}
|
}
|
||||||
|
html.template-intention.template-list main table tr {
|
||||||
|
@extend %intentions-row;
|
||||||
|
}
|
||||||
html.template-kv.template-list main table tr {
|
html.template-kv.template-list main table tr {
|
||||||
@extend %kvs-row;
|
@extend %kvs-row;
|
||||||
}
|
}
|
||||||
|
@ -65,6 +68,12 @@ html.template-node.template-show main table.sessions tr {
|
||||||
tr > * dl {
|
tr > * dl {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
%intentions-row > * {
|
||||||
|
width: calc(25% - 60px);
|
||||||
|
}
|
||||||
|
%intentions-row > *:last-child {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
%kvs-row > *:first-child {
|
%kvs-row > *:first-child {
|
||||||
width: calc(100% - 60px);
|
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'}}>
|
<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>
|
<a href={{href-to 'dc.nodes' dc.Name}}>Nodes</a>
|
||||||
</li>
|
</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'}}>
|
<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>
|
<a href={{href-to 'dc.kv' dc.Name}}>Key/Value</a>
|
||||||
</li>
|
</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