mirror of https://github.com/status-im/consul.git
ui: Remove legacy ACLs (#11096)
This commit is contained in:
parent
6e396e4456
commit
e088d8674c
|
@ -1,74 +0,0 @@
|
|||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import { SLUG_KEY } from 'consul-ui/models/acl';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
|
||||
// The old ACL system doesn't support the `ns=` query param
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default class AclAdapter extends Adapter {
|
||||
requestForQuery(request, { dc, index }) {
|
||||
// https://www.consul.io/api/acl.html#list-acls
|
||||
return request`
|
||||
GET /v1/acl/list?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
}
|
||||
|
||||
requestForQueryRecord(request, { dc, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
// https://www.consul.io/api/acl.html#read-acl-token
|
||||
return request`
|
||||
GET /v1/acl/info/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
}
|
||||
|
||||
requestForCreateRecord(request, serialized, data) {
|
||||
// https://www.consul.io/api/acl.html#create-acl-token
|
||||
return request`
|
||||
PUT /v1/acl/create?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
|
||||
${serialized}
|
||||
`;
|
||||
}
|
||||
|
||||
requestForUpdateRecord(request, serialized, data) {
|
||||
// the id is in the data, don't add it into the URL
|
||||
// https://www.consul.io/api/acl.html#update-acl-token
|
||||
return request`
|
||||
PUT /v1/acl/update?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
|
||||
${serialized}
|
||||
`;
|
||||
}
|
||||
|
||||
requestForDeleteRecord(request, serialized, data) {
|
||||
// https://www.consul.io/api/acl.html#delete-acl-token
|
||||
return request`
|
||||
PUT /v1/acl/destroy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
`;
|
||||
}
|
||||
|
||||
requestForCloneRecord(request, serialized, data) {
|
||||
// https://www.consul.io/api/acl.html#clone-acl-token
|
||||
return request`
|
||||
PUT /v1/acl/clone/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
`;
|
||||
}
|
||||
|
||||
clone(store, type, id, snapshot) {
|
||||
return this.rpc(
|
||||
function(adapter, request, serialized, unserialized) {
|
||||
return adapter.requestForCloneRecord(request, serialized, unserialized);
|
||||
},
|
||||
function(serializer, respond, serialized, unserialized) {
|
||||
return serializer.respondForCreateRecord(respond, serialized, unserialized);
|
||||
},
|
||||
snapshot,
|
||||
type.modelName
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
<div
|
||||
class="consul-acl-list"
|
||||
...attributes
|
||||
>
|
||||
<TabularCollection
|
||||
@items={{@items}}
|
||||
as |item index|
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="row">
|
||||
<td data-test-acl={{item.Name}}>
|
||||
<a href={{href-to 'dc.acls.edit' item.ID}}>{{item.Name}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{#if (eq item.Type 'management')}}
|
||||
<strong>{{item.Type}}</strong>
|
||||
{{else}}
|
||||
<span>{{item.Type}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions" as |index change checked|>
|
||||
<PopoverMenu @expanded={{if (eq checked index) true false}} @onchange={{action change index}} @keyboardAccess={{false}} @submenus={{array "logout" "use" "delete"}}>
|
||||
<BlockSlot @name="trigger">
|
||||
More
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="menu" as |confirm send keypressClick|>
|
||||
<li role="none">
|
||||
<a data-test-edit role="menuitem" tabindex="-1" href={{href-to 'dc.acls.edit' item.ID}}>
|
||||
{{#if (can "write acl" item=item)}}
|
||||
Edit
|
||||
{{else}}
|
||||
View
|
||||
{{/if}}
|
||||
</a>
|
||||
</li>
|
||||
{{#if (eq item.ID token.SecretID) }}
|
||||
<li role="none">
|
||||
<label for={{concat confirm 'logout'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-logout>Stop using</label>
|
||||
<div role="menu">
|
||||
<div class="confirmation-alert warning">
|
||||
<div>
|
||||
<header>
|
||||
Confirm logout
|
||||
</header>
|
||||
<p>
|
||||
Are you sure you want to stop using this ACL token? This will log you out.
|
||||
</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="dangerous">
|
||||
<button tabindex="-1" type="button" onclick={{action send 'logout' item}}>Logout</button>
|
||||
</li>
|
||||
<li>
|
||||
<label for={{concat confirm 'logout'}}>Cancel</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{else}}
|
||||
<li role="none">
|
||||
<label for={{concat confirm 'use'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-use>Use</label>
|
||||
<div role="menu">
|
||||
<div class="confirmation-alert warning">
|
||||
<div>
|
||||
<header>
|
||||
Confirm use
|
||||
</header>
|
||||
<p>
|
||||
Are you sure you want to use this ACL token?
|
||||
</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="dangerous">
|
||||
<button
|
||||
{{on 'click' (fn @onuse item)}}
|
||||
data-test-confirm-use
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
Use
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label for={{concat confirm 'use'}}>Cancel</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (can "duplicate acl" item=item)}}
|
||||
<li role="none">
|
||||
<button role="menuitem" tabindex="-1" type="button" data-test-clone {{action @onclone item}}>Duplicate</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{#if (can "delete acl" item=item)}}
|
||||
<li role="none" class="dangerous">
|
||||
<label for={{concat confirm 'delete'}} role="menuitem" tabindex="-1" onkeypress={{keypressClick}} data-test-delete>Delete</label>
|
||||
<div role="menu">
|
||||
<div class="confirmation-alert warning">
|
||||
<div>
|
||||
<header>
|
||||
Confirm Delete
|
||||
</header>
|
||||
<p>
|
||||
Are you sure you want to delete this token?
|
||||
</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li class="dangerous">
|
||||
<button tabindex="-1" type="button" class="type-delete" onclick={{action @ondelete item}}>Delete</button>
|
||||
</li>
|
||||
<li>
|
||||
<label for={{concat confirm 'delete'}}>Cancel</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</PopoverMenu>
|
||||
</BlockSlot>
|
||||
</TabularCollection>
|
||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||
{{#if (eq @type 'create')}}
|
||||
{{#if (eq @status 'success') }}
|
||||
Your ACL token has been added.
|
||||
{{else}}
|
||||
There was an error adding your ACL token.
|
||||
{{/if}}
|
||||
{{else if (eq @type 'update') }}
|
||||
{{#if (eq @status 'success') }}
|
||||
Your ACL token has been saved.
|
||||
{{else}}
|
||||
There was an error saving your ACL token.
|
||||
{{/if}}
|
||||
{{ else if (eq @type 'delete')}}
|
||||
{{#if (eq @status 'success') }}
|
||||
Your ACL token was deleted.
|
||||
{{else}}
|
||||
There was an error deleting your ACL token.
|
||||
{{/if}}
|
||||
{{ else if (eq @type 'use')}}
|
||||
{{#if (eq @status 'success') }}
|
||||
Now using new ACL token.
|
||||
{{else}}
|
||||
There was an error using that ACL token.
|
||||
{{/if}}
|
||||
{{ else if (eq @type 'clone')}}
|
||||
{{#if (eq @status 'success') }}
|
||||
Your ACL token was cloned.
|
||||
{{else}}
|
||||
There was an error cloning your ACL token.
|
||||
{{/if}}
|
||||
{{/if}}
|
|
@ -1,127 +0,0 @@
|
|||
<SearchBar
|
||||
class="consul-acl-search-bar"
|
||||
...attributes
|
||||
@filter={{@filter}}
|
||||
>
|
||||
<:status as |search|>
|
||||
|
||||
{{#let
|
||||
|
||||
(t (concat "components.consul.acl.search-bar." search.status.key)
|
||||
default=(array
|
||||
(concat "common.search." search.status.key)
|
||||
(concat "common.consul." search.status.key)
|
||||
)
|
||||
)
|
||||
|
||||
(t (concat "components.consul.acl.search-bar." search.status.value)
|
||||
default=(array
|
||||
(concat "common.search." search.status.value)
|
||||
(concat "common.consul." search.status.value)
|
||||
(concat "common.brand." search.status.value)
|
||||
)
|
||||
)
|
||||
|
||||
as |key value|}}
|
||||
<search.RemoveFilter
|
||||
aria-label={{t "common.ui.remove" item=(concat key " " value)}}
|
||||
>
|
||||
<dl>
|
||||
<dt>{{key}}</dt>
|
||||
<dd>{{value}}</dd>
|
||||
</dl>
|
||||
</search.RemoveFilter>
|
||||
{{/let}}
|
||||
|
||||
</:status>
|
||||
<:search as |search|>
|
||||
<search.Search
|
||||
@onsearch={{action @onsearch}}
|
||||
@value={{@search}}
|
||||
@placeholder={{t "common.search.search"}}
|
||||
>
|
||||
{{#if @filter.searchproperty}}
|
||||
<search.Select
|
||||
class="type-search-properties"
|
||||
@position="right"
|
||||
@onchange={{action @filter.searchproperty.change}}
|
||||
@multiple={{true}}
|
||||
@required={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{t "common.search.searchproperty"}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
{{#each @filter.searchproperty.default as |prop|}}
|
||||
<Option @value={{prop}} @selected={{contains prop @filter.searchproperty.value}}>
|
||||
{{t (concat "common.consul." (lowercase prop))}}
|
||||
</Option>
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
{{/if}}
|
||||
</search.Search>
|
||||
</:search>
|
||||
<:filter as |search|>
|
||||
<search.Select
|
||||
class="type-status"
|
||||
@position="left"
|
||||
@onchange={{action @filter.kind.change}}
|
||||
@multiple={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{t "components.consul.acl.search-bar.kind.name"}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
{{#each (array "management" "client") as |state|}}
|
||||
<Option class="value-{{state}}" @value={{state}} @selected={{contains state @filter.status.value}}>
|
||||
{{t (concat "components.acl.search-bar.kind.options." state)
|
||||
default=(array
|
||||
(concat "common.search." state)
|
||||
)
|
||||
}}
|
||||
</Option>
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
</:filter>
|
||||
<:sort as |search|>
|
||||
<search.Select
|
||||
class="type-sort"
|
||||
data-test-sort-control
|
||||
@position="right"
|
||||
@onchange={{action @sort.change}}
|
||||
@multiple={{false}}
|
||||
@required={{true}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{#let (from-entries (array
|
||||
(array "Name:asc" (t "common.sort.alpha.asc"))
|
||||
(array "Name:desc" (t "common.sort.alpha.desc"))
|
||||
))
|
||||
as |selectable|
|
||||
}}
|
||||
{{get selectable @sort.value}}
|
||||
{{/let}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
<Optgroup @label={{t "common.consul.name"}}>
|
||||
<Option @value="Name:asc" @selected={{eq "Name:asc" @sort.value}}>{{t "common.sort.alpha.asc"}}</Option>
|
||||
<Option @value="Name:desc" @selected={{eq "Name:desc" @sort.value}}>{{t "common.sort.alpha.desc"}}</Option>
|
||||
</Optgroup>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
</:sort>
|
||||
</SearchBar>
|
|
@ -1,2 +0,0 @@
|
|||
import Controller from './edit';
|
||||
export default class CreateController extends Controller {}
|
|
@ -1,30 +0,0 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
builder: service('form'),
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.form = this.builder.form('acl');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
// essentially this replaces the data with changesets
|
||||
this._super(
|
||||
Object.keys(model).reduce((prev, key, i) => {
|
||||
switch (key) {
|
||||
case 'item':
|
||||
prev[key] = this.form.setData(prev[key]).getData();
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
}, model)
|
||||
);
|
||||
},
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
const event = this.dom.normalizeEvent(e, value);
|
||||
this.form.handleEvent(event);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
kind: {
|
||||
management: (item, value) => item.Type === value,
|
||||
client: (item, value) => item.Type === value,
|
||||
},
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
import validations from 'consul-ui/validations/acl';
|
||||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
const builder = builderFactory();
|
||||
export default function(container, name = '', v = validations, form = builder) {
|
||||
return form(name, {}).setValidators(v);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {
|
||||
settings: service('settings'),
|
||||
actions: {
|
||||
use: function(item) {
|
||||
return this.settings.persist({
|
||||
token: {
|
||||
Namespace: 'default',
|
||||
AccessorID: null,
|
||||
SecretID: get(item, 'ID'),
|
||||
},
|
||||
});
|
||||
},
|
||||
logout: function(item) {
|
||||
return this.settings.delete('token');
|
||||
},
|
||||
clone: function(item) {
|
||||
return this.feedback.execute(() => {
|
||||
return this.repo.clone(item).then(item => {
|
||||
// cloning is similar to delete in that
|
||||
// if you clone from the listing page, stay on the listing page
|
||||
// whereas if you clone form another token, take me back to the listing page
|
||||
// so I can see it
|
||||
return this.afterDelete(...arguments);
|
||||
});
|
||||
}, 'clone');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
|
||||
export default class Acl extends Model {
|
||||
@attr('string') uid;
|
||||
@attr('string') ID;
|
||||
|
||||
@attr('string') Datacenter;
|
||||
// TODO: Why didn't I have to do this for KV's? This is to ensure that Name
|
||||
// is '' and not null when creating maybe its due to the fact that `Key` is
|
||||
// the primaryKey in Kv's
|
||||
@attr('string', { defaultValue: () => '' }) Name;
|
||||
@attr('string') Type;
|
||||
@attr('string') Rules;
|
||||
@attr('number') CreateIndex;
|
||||
@attr('number') ModifyIndex;
|
||||
}
|
|
@ -1,227 +1,236 @@
|
|||
/* globals requirejs */
|
||||
import EmberRouter from '@ember/routing/router';
|
||||
import config from './config/environment';
|
||||
import { runInDebug } from '@ember/debug';
|
||||
import merge from 'deepmerge';
|
||||
import { env } from 'consul-ui/env';
|
||||
import walk, { dump } from 'consul-ui/utils/routing/walk';
|
||||
|
||||
export const routes = {
|
||||
// Our parent datacenter resource sets the namespace
|
||||
// for the entire application
|
||||
dc: {
|
||||
_options: { path: '/:dc' },
|
||||
// Services represent a consul service
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
// Show an individual service
|
||||
show: {
|
||||
_options: { path: '/:name' },
|
||||
instances: {
|
||||
_options: { path: '/instances' },
|
||||
const doc = document;
|
||||
const appName = config.modulePrefix;
|
||||
const appNameJS = appName
|
||||
.split('-')
|
||||
.map((item, i) => (i ? `${item.substr(0, 1).toUpperCase()}${item.substr(1)}` : item))
|
||||
.join('');
|
||||
|
||||
export const routes = merge.all(
|
||||
[
|
||||
{
|
||||
// Our parent datacenter resource sets the namespace
|
||||
// for the entire application
|
||||
dc: {
|
||||
_options: { path: '/:dc' },
|
||||
// Services represent a consul service
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
// Show an individual service
|
||||
show: {
|
||||
_options: { path: '/:name' },
|
||||
instances: {
|
||||
_options: { path: '/instances' },
|
||||
},
|
||||
intentions: {
|
||||
_options: { path: '/intentions' },
|
||||
edit: {
|
||||
_options: { path: '/:intention_id' },
|
||||
},
|
||||
create: {
|
||||
_options: { path: '/create' },
|
||||
},
|
||||
},
|
||||
topology: {
|
||||
_options: { path: '/topology' },
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
},
|
||||
upstreams: {
|
||||
_options: { path: '/upstreams' },
|
||||
},
|
||||
routing: {
|
||||
_options: { path: '/routing' },
|
||||
},
|
||||
tags: {
|
||||
_options: { path: '/tags' },
|
||||
},
|
||||
},
|
||||
instance: {
|
||||
_options: { path: '/:name/instances/:node/:id' },
|
||||
healthchecks: {
|
||||
_options: { path: '/health-checks' },
|
||||
},
|
||||
upstreams: {
|
||||
_options: { path: '/upstreams' },
|
||||
},
|
||||
exposedpaths: {
|
||||
_options: { path: '/exposed-paths' },
|
||||
},
|
||||
addresses: {
|
||||
_options: { path: '/addresses' },
|
||||
},
|
||||
metadata: {
|
||||
_options: { path: '/metadata' },
|
||||
},
|
||||
},
|
||||
notfound: {
|
||||
_options: { path: '/:name/:node/:id' },
|
||||
},
|
||||
},
|
||||
// Nodes represent a consul node
|
||||
nodes: {
|
||||
_options: { path: '/nodes' },
|
||||
// Show an individual node
|
||||
show: {
|
||||
_options: { path: '/:name' },
|
||||
healthchecks: {
|
||||
_options: { path: '/health-checks' },
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/service-instances' },
|
||||
},
|
||||
rtt: {
|
||||
_options: { path: '/round-trip-time' },
|
||||
},
|
||||
sessions: {
|
||||
_options: { path: '/lock-sessions' },
|
||||
},
|
||||
metadata: {
|
||||
_options: { path: '/metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Intentions represent a consul intention
|
||||
intentions: {
|
||||
_options: { path: '/intentions' },
|
||||
edit: {
|
||||
_options: { path: '/:intention_id' },
|
||||
_options: {
|
||||
path: '/:intention_id',
|
||||
abilities: ['read intentions'],
|
||||
},
|
||||
},
|
||||
create: {
|
||||
_options: { path: '/create' },
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create intentions'],
|
||||
},
|
||||
},
|
||||
},
|
||||
topology: {
|
||||
_options: { path: '/topology' },
|
||||
// Key/Value
|
||||
kv: {
|
||||
_options: { path: '/kv' },
|
||||
folder: {
|
||||
_options: { path: '/*key' },
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/*key/edit' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/*key/create',
|
||||
abilities: ['create kvs'],
|
||||
},
|
||||
},
|
||||
'root-create': {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create kvs'],
|
||||
},
|
||||
},
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
// ACLs
|
||||
acls: {
|
||||
_options: {
|
||||
path: '/acls',
|
||||
abilities: ['access acls'],
|
||||
},
|
||||
policies: {
|
||||
_options: {
|
||||
path: '/policies',
|
||||
abilities: ['read policies'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create policies'],
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
_options: {
|
||||
path: '/roles',
|
||||
abilities: ['read roles'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create roles'],
|
||||
},
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
_options: {
|
||||
path: '/tokens',
|
||||
abilities: ['access acls'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create tokens'],
|
||||
},
|
||||
},
|
||||
},
|
||||
'auth-methods': {
|
||||
_options: {
|
||||
path: '/auth-methods',
|
||||
abilities: ['read auth-methods'],
|
||||
},
|
||||
show: {
|
||||
_options: { path: '/:id' },
|
||||
'auth-method': {
|
||||
_options: { path: '/auth-method' },
|
||||
},
|
||||
'binding-rules': {
|
||||
_options: { path: '/binding-rules' },
|
||||
},
|
||||
'nspace-rules': {
|
||||
_options: { path: '/nspace-rules' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
upstreams: {
|
||||
_options: { path: '/upstreams' },
|
||||
},
|
||||
routing: {
|
||||
_options: { path: '/routing' },
|
||||
},
|
||||
tags: {
|
||||
_options: { path: '/tags' },
|
||||
'routing-config': {
|
||||
_options: { path: '/routing-config/:name' },
|
||||
},
|
||||
},
|
||||
instance: {
|
||||
_options: { path: '/:name/instances/:node/:id' },
|
||||
healthchecks: {
|
||||
_options: { path: '/health-checks' },
|
||||
},
|
||||
upstreams: {
|
||||
_options: { path: '/upstreams' },
|
||||
},
|
||||
exposedpaths: {
|
||||
_options: { path: '/exposed-paths' },
|
||||
},
|
||||
addresses: {
|
||||
_options: { path: '/addresses' },
|
||||
},
|
||||
metadata: {
|
||||
_options: { path: '/metadata' },
|
||||
},
|
||||
// Shows a datacenter picker. If you only have one
|
||||
// it just redirects you through.
|
||||
index: {
|
||||
_options: { path: '/' },
|
||||
},
|
||||
// The settings page is global.
|
||||
settings: {
|
||||
_options: { path: '/setting' },
|
||||
},
|
||||
notfound: {
|
||||
_options: { path: '/:name/:node/:id' },
|
||||
_options: { path: '/*notfound' },
|
||||
},
|
||||
},
|
||||
// Nodes represent a consul node
|
||||
nodes: {
|
||||
_options: { path: '/nodes' },
|
||||
// Show an individual node
|
||||
show: {
|
||||
_options: { path: '/:name' },
|
||||
healthchecks: {
|
||||
_options: { path: '/health-checks' },
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/service-instances' },
|
||||
},
|
||||
rtt: {
|
||||
_options: { path: '/round-trip-time' },
|
||||
},
|
||||
sessions: {
|
||||
_options: { path: '/lock-sessions' },
|
||||
},
|
||||
metadata: {
|
||||
_options: { path: '/metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Intentions represent a consul intention
|
||||
intentions: {
|
||||
_options: { path: '/intentions' },
|
||||
edit: {
|
||||
_options: {
|
||||
path: '/:intention_id',
|
||||
abilities: ['read intentions'],
|
||||
},
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create intentions'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Key/Value
|
||||
kv: {
|
||||
_options: { path: '/kv' },
|
||||
folder: {
|
||||
_options: { path: '/*key' },
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/*key/edit' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/*key/create',
|
||||
abilities: ['create kvs'],
|
||||
},
|
||||
},
|
||||
'root-create': {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create kvs'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// ACLs
|
||||
acls: {
|
||||
_options: {
|
||||
path: '/acls',
|
||||
abilities: ['access acls'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:acl' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create acls'],
|
||||
},
|
||||
},
|
||||
policies: {
|
||||
_options: {
|
||||
path: '/policies',
|
||||
abilities: ['read policies'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create policies'],
|
||||
},
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
_options: {
|
||||
path: '/roles',
|
||||
abilities: ['read roles'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create roles'],
|
||||
},
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
_options: {
|
||||
path: '/tokens',
|
||||
abilities: env('CONSUL_ACLS_ENABLED') ? ['read tokens'] : ['access acls'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
path: '/create',
|
||||
abilities: ['create tokens'],
|
||||
},
|
||||
},
|
||||
},
|
||||
'auth-methods': {
|
||||
_options: {
|
||||
path: '/auth-methods',
|
||||
abilities: ['read auth-methods'],
|
||||
},
|
||||
show: {
|
||||
_options: { path: '/:id' },
|
||||
'auth-method': {
|
||||
_options: { path: '/auth-method' },
|
||||
},
|
||||
'binding-rules': {
|
||||
_options: { path: '/binding-rules' },
|
||||
},
|
||||
'nspace-rules': {
|
||||
_options: { path: '/nspace-rules' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'routing-config': {
|
||||
_options: { path: '/routing-config/:name' },
|
||||
},
|
||||
},
|
||||
// Shows a datacenter picker. If you only have one
|
||||
// it just redirects you through.
|
||||
index: {
|
||||
_options: { path: '/' },
|
||||
},
|
||||
// The settings page is global.
|
||||
settings: {
|
||||
_options: { path: '/setting' },
|
||||
},
|
||||
notfound: {
|
||||
_options: { path: '/*notfound' },
|
||||
},
|
||||
};
|
||||
].concat(
|
||||
...[...doc.querySelectorAll(`script[data-${appName}-routes]`)].map($item =>
|
||||
JSON.parse($item.dataset[`${appNameJS}Routes`])
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (env('CONSUL_NSPACES_ENABLED')) {
|
||||
routes.dc.nspaces = {
|
||||
_options: {
|
||||
|
@ -242,7 +251,7 @@ if (env('CONSUL_NSPACES_ENABLED')) {
|
|||
runInDebug(() => {
|
||||
// check to see if we are running docfy and if so add its routes to our
|
||||
// route config
|
||||
const docfyOutput = requirejs.entries['consul-ui/docfy-output'];
|
||||
const docfyOutput = requirejs.entries[`${appName}/docfy-output`];
|
||||
if (typeof docfyOutput !== 'undefined') {
|
||||
const output = {};
|
||||
docfyOutput.callback(output);
|
||||
|
@ -269,13 +278,6 @@ runInDebug(() => {
|
|||
})(routes, output.default.nested);
|
||||
}
|
||||
});
|
||||
export default class Router extends EmberRouter {
|
||||
location = env('locationType');
|
||||
rootURL = env('rootURL');
|
||||
}
|
||||
|
||||
Router.map(walk(routes));
|
||||
|
||||
// To print the Ember route DSL use `Routes()` in Web Inspectors console
|
||||
// or `javascript:Routes()` in the location bar of your browser
|
||||
runInDebug(() => {
|
||||
|
@ -295,3 +297,9 @@ runInDebug(() => {
|
|||
return;
|
||||
};
|
||||
});
|
||||
|
||||
export default class Router extends EmberRouter {
|
||||
location = env('locationType');
|
||||
rootURL = env('rootURL');
|
||||
}
|
||||
Router.map(walk(routes));
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
export default class AclsRoute extends Route.extend(WithBlockingActions) {}
|
|
@ -1,39 +0,0 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
|
||||
|
||||
export default class CreateRoute extends Route.extend(WithAclActions) {
|
||||
templateName = 'dc/acls/edit';
|
||||
|
||||
@service('repository/acl')
|
||||
repo;
|
||||
|
||||
beforeModel() {
|
||||
this.repo.invalidate();
|
||||
}
|
||||
|
||||
model(params) {
|
||||
this.item = this.repo.create({
|
||||
Datacenter: this.modelFor('dc').dc.Name,
|
||||
});
|
||||
return hash({
|
||||
create: true,
|
||||
item: this.item,
|
||||
types: ['management', 'client'],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
if (get(this.item, 'isNew')) {
|
||||
this.item.destroyRecord();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
|
||||
|
||||
export default class EditRoute extends Route.extend(WithAclActions) {
|
||||
@service('repository/acl')
|
||||
repo;
|
||||
|
||||
@service('settings')
|
||||
settings;
|
||||
|
||||
model(params) {
|
||||
return hash({
|
||||
item: this.repo.findBySlug({
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
id: params.id,
|
||||
}),
|
||||
types: ['management', 'client'],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
|
||||
|
||||
export default class IndexRoute extends Route.extend(WithAclActions) {
|
||||
@service('repository/acl') repo;
|
||||
@service('settings') settings;
|
||||
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
kind: 'kind',
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
async beforeModel(transition) {
|
||||
const token = await this.settings.findBySlug('token');
|
||||
// If you don't have a token set or you have a
|
||||
// token set with AccessorID set to not null (new ACL mode)
|
||||
// then rewrite to the new acls
|
||||
if (!token || get(token, 'AccessorID') !== null) {
|
||||
// If you return here, you get a TransitionAborted error in the tests only
|
||||
// everything works fine either way checking things manually
|
||||
this.replaceWith('dc.acls.tokens');
|
||||
}
|
||||
}
|
||||
|
||||
async model(params) {
|
||||
const _items = this.repo.findAllByDatacenter({ dc: this.modelFor('dc').dc.Name });
|
||||
const _token = this.settings.findBySlug('token');
|
||||
return {
|
||||
items: await _items,
|
||||
token: await _token,
|
||||
};
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,22 @@ export default class BaseRoute extends Route {
|
|||
@service('repository/permission') permissions;
|
||||
@service('router') router;
|
||||
|
||||
redirect(model, transition) {
|
||||
// remove any references to index as it is the same as the root routeName
|
||||
const routeName = this.routeName
|
||||
.split('.')
|
||||
.filter(item => item !== 'index')
|
||||
.join('.');
|
||||
const to = get(routes, `${routeName}._options.redirect`);
|
||||
if (typeof to !== 'undefined') {
|
||||
// TODO: Does this need to return?
|
||||
// Almost remember things getting strange if you returned from here
|
||||
// which is why I didn't do it originally so be sure to look properly if
|
||||
// you feel like adding a return
|
||||
this.replaceWith(`${routeName}${to}`, model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects a custom `abilities` array on the router for this route. Every
|
||||
* abililty needs to 'pass' for the route not to throw a 403 error. Anything
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import Serializer from './application';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/acl';
|
||||
|
||||
export default class AclSerializer extends Serializer {
|
||||
primaryKey = PRIMARY_KEY;
|
||||
slugKey = SLUG_KEY;
|
||||
|
||||
respondForQueryRecord(respond, query) {
|
||||
return super.respondForQueryRecord(
|
||||
cb => respond((headers, body) => cb(headers, body[0])),
|
||||
query
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import Service from '@ember/service';
|
||||
import { andOr } from 'consul-ui/utils/filter';
|
||||
|
||||
import acl from 'consul-ui/filter/predicates/acl';
|
||||
import service from 'consul-ui/filter/predicates/service';
|
||||
import serviceInstance from 'consul-ui/filter/predicates/service-instance';
|
||||
import healthCheck from 'consul-ui/filter/predicates/health-check';
|
||||
|
@ -13,7 +12,6 @@ import policy from 'consul-ui/filter/predicates/policy';
|
|||
import authMethod from 'consul-ui/filter/predicates/auth-method';
|
||||
|
||||
const predicates = {
|
||||
acl: andOr(acl),
|
||||
service: andOr(service),
|
||||
['service-instance']: andOr(serviceInstance),
|
||||
['health-check']: andOr(healthCheck),
|
||||
|
|
|
@ -2,7 +2,6 @@ import Service, { inject as service } from '@ember/service';
|
|||
import builderFactory from 'consul-ui/utils/form/builder';
|
||||
|
||||
import kv from 'consul-ui/forms/kv';
|
||||
import acl from 'consul-ui/forms/acl';
|
||||
import token from 'consul-ui/forms/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import role from 'consul-ui/forms/role';
|
||||
|
@ -13,7 +12,6 @@ const builder = builderFactory();
|
|||
|
||||
const forms = {
|
||||
kv: kv,
|
||||
acl: acl,
|
||||
token: token,
|
||||
policy: policy,
|
||||
role: role,
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import { get } from '@ember/object';
|
||||
import { PRIMARY_KEY } from 'consul-ui/models/acl';
|
||||
const modelName = 'acl';
|
||||
export default class AclService extends RepositoryService {
|
||||
getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
|
||||
getPrimaryKey() {
|
||||
return PRIMARY_KEY;
|
||||
}
|
||||
|
||||
clone(item) {
|
||||
return this.store.clone(this.getModelName(), get(item, this.getPrimaryKey()));
|
||||
}
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import statusFactory from 'consul-ui/utils/acls-status';
|
||||
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/auth-method';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
const isValidServerError = isValidServerErrorFactory();
|
||||
const status = statusFactory(isValidServerError, Promise);
|
||||
const MODEL_NAME = 'auth-method';
|
||||
|
||||
export default class AuthMethodService extends RepositoryService {
|
||||
|
@ -30,8 +26,4 @@ export default class AuthMethodService extends RepositoryService {
|
|||
async findBySlug() {
|
||||
return super.findBySlug(...arguments);
|
||||
}
|
||||
|
||||
status(obj) {
|
||||
return status(obj);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import statusFactory from 'consul-ui/utils/acls-status';
|
||||
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/binding-rule';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
const isValidServerError = isValidServerErrorFactory();
|
||||
const status = statusFactory(isValidServerError, Promise);
|
||||
const MODEL_NAME = 'binding-rule';
|
||||
|
||||
export default class BindingRuleService extends RepositoryService {
|
||||
|
@ -25,8 +21,4 @@ export default class BindingRuleService extends RepositoryService {
|
|||
async findAllByAuthMethod() {
|
||||
return super.findAll(...arguments);
|
||||
}
|
||||
|
||||
status(obj) {
|
||||
return status(obj);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import statusFactory from 'consul-ui/utils/acls-status';
|
||||
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/policy';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
const isValidServerError = isValidServerErrorFactory();
|
||||
const status = statusFactory(isValidServerError, Promise);
|
||||
const MODEL_NAME = 'policy';
|
||||
|
||||
export default class PolicyService extends RepositoryService {
|
||||
|
@ -47,10 +43,6 @@ export default class PolicyService extends RepositoryService {
|
|||
.getData();
|
||||
}
|
||||
|
||||
status(obj) {
|
||||
return status(obj);
|
||||
}
|
||||
|
||||
persist(item) {
|
||||
// only if a policy doesn't have a template, save it
|
||||
// right now only ServiceIdentities have templates and
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import { inject as service } from '@ember/service';
|
||||
import statusFactory from 'consul-ui/utils/acls-status';
|
||||
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/role';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
const isValidServerError = isValidServerErrorFactory();
|
||||
const status = statusFactory(isValidServerError, Promise);
|
||||
const MODEL_NAME = 'role';
|
||||
|
||||
export default class RoleService extends RepositoryService {
|
||||
|
@ -45,8 +41,4 @@ export default class RoleService extends RepositoryService {
|
|||
.setData(item)
|
||||
.getData();
|
||||
}
|
||||
|
||||
status(obj) {
|
||||
return status(obj);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,8 @@ import RepositoryService from 'consul-ui/services/repository';
|
|||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/token';
|
||||
import statusFactory from 'consul-ui/utils/acls-status';
|
||||
import isValidServerErrorFactory from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import dataSource from 'consul-ui/decorators/data-source';
|
||||
|
||||
const isValidServerError = isValidServerErrorFactory();
|
||||
const status = statusFactory(isValidServerError, Promise);
|
||||
const MODEL_NAME = 'token';
|
||||
|
||||
export default class TokenService extends RepositoryService {
|
||||
|
@ -24,10 +20,6 @@ export default class TokenService extends RepositoryService {
|
|||
return SLUG_KEY;
|
||||
}
|
||||
|
||||
status(obj) {
|
||||
return status(obj);
|
||||
}
|
||||
|
||||
@dataSource('/:partition/:ns/:dc/tokens')
|
||||
async findAll() {
|
||||
return super.findAll(...arguments);
|
||||
|
@ -53,21 +45,14 @@ export default class TokenService extends RepositoryService {
|
|||
|
||||
@dataSource('/:partition/:ns/:dc/token/self/:secret')
|
||||
self(params) {
|
||||
// TODO: Does this need ns passing through?
|
||||
// This request does not need ns or partition passing through as its
|
||||
// inferred from the token itself.
|
||||
return this.store
|
||||
.self(this.getModelName(), {
|
||||
secret: params.secret,
|
||||
dc: params.dc,
|
||||
})
|
||||
.catch(e => {
|
||||
// If we get this 500 RPC error, it means we are a legacy ACL cluster
|
||||
// set AccessorID to null - which for the frontend means legacy mode
|
||||
if (isValidServerError(e)) {
|
||||
return {
|
||||
AccessorID: null,
|
||||
SecretID: params.secret,
|
||||
};
|
||||
}
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import Service from '@ember/service';
|
|||
import service from 'consul-ui/sort/comparators/service';
|
||||
import serviceInstance from 'consul-ui/sort/comparators/service-instance';
|
||||
import upstreamInstance from 'consul-ui/sort/comparators/upstream-instance';
|
||||
import acl from 'consul-ui/sort/comparators/acl';
|
||||
import kv from 'consul-ui/sort/comparators/kv';
|
||||
import healthCheck from 'consul-ui/sort/comparators/health-check';
|
||||
import intention from 'consul-ui/sort/comparators/intention';
|
||||
|
@ -34,7 +33,6 @@ const comparators = {
|
|||
['upstream-instance']: upstreamInstance(options),
|
||||
['health-check']: healthCheck(options),
|
||||
['auth-method']: authMethod(options),
|
||||
acl: acl(options),
|
||||
kv: kv(options),
|
||||
intention: intention(options),
|
||||
token: token(options),
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export default ({ properties }) => (key = 'Name:asc') => {
|
||||
return properties(['Name'])(key);
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
<form>
|
||||
<fieldset
|
||||
disabled={{if (not (can "write acl" item=item)) "disabled"}}
|
||||
>
|
||||
<label class="type-text{{if item.error.Name ' has-error'}}">
|
||||
<span>Name</span>
|
||||
<Input @value={{item.Name}} @name="name" @autofocus="autofocus" />
|
||||
</label>
|
||||
<div role="radiogroup" class={{if item.error.Type ' has-error'}}>
|
||||
{{#each types as |type|}}
|
||||
<label>
|
||||
<span>{{capitalize type}}</span>
|
||||
<input type="radio" name="Type" value="{{type}}" checked={{if (eq item.Type type) 'checked'}} onchange={{ action 'change' }}/>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
<label class="type-text">
|
||||
<span>Policy <a href="{{env 'CONSUL_DOCS_URL'}}/guides/acl.html#rule-specification" rel="help noopener noreferrer" target="_blank">(HCL Format)</a></span>
|
||||
<CodeEditor @class={{if item.error.Rules "error"}} @name="Rules" @value={{item.Rules}} @syntax="hcl" @onkeyup={{action "change" "Rules"}} />
|
||||
</label>
|
||||
{{#if create }}
|
||||
<label class="type-text">
|
||||
<span>ID</span>
|
||||
<Input @value={{item.ID}} />
|
||||
<em>We'll generate a UUID if this field is left empty.</em>
|
||||
</label>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
<div>
|
||||
{{#if (and create (can "create acls")) }}
|
||||
{{! we only need to check for an empty name here as ember munges autofocus, once we have autofocus back revisit this}}
|
||||
<button type="submit" {{ action "create" item}} disabled={{if (or item.isPristine item.isInvalid (eq item.Name '')) 'disabled'}}>Save</button>
|
||||
{{else}}
|
||||
{{#if (can "write acl" item=item)}}
|
||||
<button type="submit" {{ action "update" item}} disabled={{if item.isInvalid 'disabled'}}>Save</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<button type="reset" {{ action "cancel" item}}>Cancel</button>
|
||||
{{# if (and (not create) (can "delete acl" item=item) ) }}
|
||||
<ConfirmationDialog @message="Are you sure you want to delete this ACL token?">
|
||||
<BlockSlot @name="action" as |confirm|>
|
||||
<button type="button" data-test-delete class="type-delete" {{action confirm 'delete' item parent}}>Delete</button>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||
<DeleteConfirmation @message={{message}} @execute={{execute}} @cancel={{cancel}} />
|
||||
</BlockSlot>
|
||||
</ConfirmationDialog>
|
||||
{{/if}}
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
@title={{if create 'New ACL' 'Edit ACL'}}
|
||||
as |route|>
|
||||
<AppView>
|
||||
<BlockSlot @name="notification" as |status type item error|>
|
||||
<Consul::Acl::Notifications
|
||||
@status={{status}}
|
||||
@type={{type}}
|
||||
@error={{error}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="breadcrumbs">
|
||||
<ol>
|
||||
<li><a data-test-back href={{href-to 'dc.acls'}}>All Tokens</a></li>
|
||||
</ol>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
{{#if item.Name }}
|
||||
{{item.Name}}
|
||||
{{else}}
|
||||
New token
|
||||
{{/if}}
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
{{#if (not create) }}
|
||||
<CopyButton @value={{item.ID}} @name="token ID">
|
||||
Copy token ID
|
||||
</CopyButton>
|
||||
{{#if (can "duplicate acl" item=item)}}
|
||||
<button type="button" {{ action "clone" item }}>Clone token</button>
|
||||
<ConfirmationDialog @message="Are you sure you want to use this ACL token?">
|
||||
<BlockSlot @name="action" as |confirm|>
|
||||
<button data-test-use type="button" {{ action confirm 'use' item }}>Use token</button>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="dialog" as |execute cancel message|>
|
||||
<p>
|
||||
{{message}}
|
||||
</p>
|
||||
<button type="button" class="type-delete" {{action execute}}>Confirm Use</button>
|
||||
<button type="button" class="type-cancel" {{action cancel}}>Cancel</button>
|
||||
</BlockSlot>
|
||||
</ConfirmationDialog>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
{{ partial 'dc/acls/form'}}
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
</Route>
|
|
@ -1,101 +1,5 @@
|
|||
<Route
|
||||
@name={{routeName}}
|
||||
@title="ACLs"
|
||||
as |route|>
|
||||
{{#let
|
||||
|
||||
(hash
|
||||
value=(or sortBy "Name:asc")
|
||||
change=(action (mut sortBy) value="target.selected")
|
||||
)
|
||||
|
||||
(hash
|
||||
kind=(hash
|
||||
value=(if kind (split kind ',') undefined)
|
||||
change=(action (mut kind) value="target.selectedItems")
|
||||
)
|
||||
)
|
||||
|
||||
items
|
||||
|
||||
as |sort filters items|}}
|
||||
|
||||
<AppView>
|
||||
<BlockSlot @name="notification" as |status type item error|>
|
||||
<Consul::Acl::Notifications
|
||||
@status={{status}}
|
||||
@type={{type}}
|
||||
@error={{error}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
ACL Tokens <em>{{format-number items.length}} total</em>
|
||||
</h1>
|
||||
<label for="toolbar-toggle"></label>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
{{#if (can "create acls")}}
|
||||
<a data-test-create href="{{href-to 'dc.acls.create'}}" class="type-create">Create</a>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="toolbar">
|
||||
{{#if (gt items.length 0) }}
|
||||
<Consul::Acl::SearchBar
|
||||
@search={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
|
||||
@sort={{sort}}
|
||||
|
||||
@filter={{filters}}
|
||||
/>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<DataCollection
|
||||
@type="acl"
|
||||
@sort={{sort.value}}
|
||||
@filters={{filters}}
|
||||
@search={{search}}
|
||||
@items={{items}}
|
||||
as |collection|>
|
||||
<collection.Collection>
|
||||
<Consul::Acl::List
|
||||
@items={{collection.items}}
|
||||
|
||||
@ondelete={{route-action 'delete'}}
|
||||
@onuse={{route-action 'use'}}
|
||||
@onclone={{route-action 'clone'}}
|
||||
>
|
||||
</Consul::Acl::List>
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
<EmptyState
|
||||
@login={{route.model.app.login.open}}
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<h2>
|
||||
{{#if (gt items.length 0)}}
|
||||
No ACLs found
|
||||
{{else}}
|
||||
Welcome to ACLs
|
||||
{{/if}}
|
||||
</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
{{#if (gt items.length 0)}}
|
||||
No ACLs where found matching that search, or you may not have access to view the ACLs you are searching for.
|
||||
{{else}}
|
||||
There don't seem to be any ACLs yet, or you may not have access to view ACLs yet.
|
||||
{{/if}}
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</collection.Empty>
|
||||
</DataCollection>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
|
||||
{{/let}}
|
||||
{{did-insert (route-action 'replaceWith' 'dc.acls.tokens')}}
|
||||
</Route>
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
// This is used by all acl routes to check whether
|
||||
// acls are enabled on the server, and whether the user
|
||||
// has a valid token
|
||||
// Right now this is very acl specific, but is likely to be
|
||||
// made a bit more less specific
|
||||
|
||||
export default function(isValidServerError, P = Promise) {
|
||||
return function(obj) {
|
||||
const propName = Object.keys(obj)[0];
|
||||
const p = obj[propName];
|
||||
let authorize;
|
||||
let enable;
|
||||
return {
|
||||
isAuthorized: new P(function(resolve) {
|
||||
authorize = function(bool) {
|
||||
resolve(bool);
|
||||
};
|
||||
}),
|
||||
isEnabled: new P(function(resolve) {
|
||||
enable = function(bool) {
|
||||
resolve(bool);
|
||||
};
|
||||
}),
|
||||
[propName]: p
|
||||
.catch(function(e) {
|
||||
if (e.errors && e.errors[0]) {
|
||||
switch (e.errors[0].status) {
|
||||
case '500':
|
||||
if (isValidServerError(e)) {
|
||||
enable(true);
|
||||
authorize(false);
|
||||
} else {
|
||||
enable(false);
|
||||
authorize(false);
|
||||
return P.reject(e);
|
||||
}
|
||||
break;
|
||||
case '403':
|
||||
enable(true);
|
||||
authorize(false);
|
||||
break;
|
||||
case '401':
|
||||
enable(false);
|
||||
authorize(false);
|
||||
break;
|
||||
default:
|
||||
enable(false);
|
||||
authorize(false);
|
||||
throw e;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
enable(false);
|
||||
authorize(false);
|
||||
throw e;
|
||||
})
|
||||
.then(function(res) {
|
||||
enable(true);
|
||||
authorize(true);
|
||||
return res;
|
||||
}),
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
// very specific error check just for one specific ACL case
|
||||
// likely to be reused at a later date, so lets use the specific
|
||||
// case we need right now as default
|
||||
const UNKNOWN_METHOD_ERROR = "rpc error making call: rpc: can't find method ACL";
|
||||
export default function(response = UNKNOWN_METHOD_ERROR) {
|
||||
return function(e) {
|
||||
if (e && e.errors && e.errors[0] && e.errors[0].detail) {
|
||||
return e.errors[0].detail.indexOf(response) !== -1;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { validatePresence, validateLength } from 'ember-changeset-validations/validators';
|
||||
export default {
|
||||
Name: [validatePresence(true), validateLength({ min: 1 })],
|
||||
Type: validatePresence(true),
|
||||
};
|
|
@ -163,6 +163,9 @@ module.exports = function(defaults, $ = process.env) {
|
|||
app.import('vendor/metrics-providers/prometheus.js', {
|
||||
outputFile: 'assets/metrics-providers/prometheus.js',
|
||||
});
|
||||
app.import('vendor/acls/routes.js', {
|
||||
outputFile: 'assets/acls/routes.js',
|
||||
});
|
||||
app.import('vendor/init.js', {
|
||||
outputFile: 'assets/init.js',
|
||||
});
|
||||
|
|
|
@ -41,6 +41,26 @@ ${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.oper
|
|||
"codemirror/mode/yaml/yaml.js": "${rootURL}assets/codemirror/mode/yaml/yaml.js"
|
||||
}
|
||||
</script>
|
||||
${
|
||||
environment === 'production'
|
||||
? `
|
||||
{{if .ACLsEnabled}}
|
||||
<script data-${appName}-routing src="${rootURL}assets/acls/routes.js"></script>
|
||||
{{end}}
|
||||
`
|
||||
: `
|
||||
<script>
|
||||
if(document.cookie['CONSUL_ACLS_ENABLED']) {
|
||||
const appName = '${appName}';
|
||||
const appNameJS = appName.split('-').map((item, i) => i ? \`\${item.substr(0, 1).toUpperCase()}\${item.substr(1)}\` : item).join('');
|
||||
const $script = document.createElement('script');
|
||||
$script.setAttribute('src', '${rootURL}assets/acls/routes.js');
|
||||
$script.dataset[\`\${appNameJS}Routes\`] = null;
|
||||
document.body.appendChild($script);
|
||||
}
|
||||
</script>
|
||||
`
|
||||
}
|
||||
<script src="${rootURL}assets/init.js"></script>
|
||||
<script src="${rootURL}assets/vendor.js"></script>
|
||||
${environment === 'test' ? `<script src="${rootURL}assets/test-support.js"></script>` : ``}
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
"d3-selection": "^2.0.0",
|
||||
"d3-shape": "^2.0.0",
|
||||
"dayjs": "^1.9.3",
|
||||
"deepmerge": "^4.2.2",
|
||||
"ember-assign-helper": "^0.3.0",
|
||||
"ember-auto-import": "^1.5.3",
|
||||
"ember-can": "^3.0.0",
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = function(app, options) {
|
|||
// sets the base CSP policy for the UI
|
||||
app.use(function(request, response, next) {
|
||||
response.set({
|
||||
'Content-Security-Policy': `default-src 'self' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`,
|
||||
'Content-Security-Policy': `default-src 'self' 'unsafe-inline' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`,
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
module('Integration | Adapter | acl', function(hooks) {
|
||||
setupTest(hooks);
|
||||
const dc = 'dc-1';
|
||||
const id = 'token-name';
|
||||
test('requestForQuery returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `GET /v1/acl/list?dc=${dc}`;
|
||||
const actual = adapter.requestForQuery(request, {
|
||||
dc: dc,
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('requestForQueryRecord returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `GET /v1/acl/info/${id}?dc=${dc}`;
|
||||
const actual = adapter.requestForQueryRecord(request, {
|
||||
dc: dc,
|
||||
id: id,
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
assert.throws(function() {
|
||||
adapter.requestForQueryRecord(request, {
|
||||
dc: dc,
|
||||
});
|
||||
});
|
||||
});
|
||||
test('requestForCreateRecord returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `PUT /v1/acl/create?dc=${dc}`;
|
||||
const actual = adapter
|
||||
.requestForCreateRecord(
|
||||
request,
|
||||
{},
|
||||
{
|
||||
Datacenter: dc,
|
||||
ID: id,
|
||||
}
|
||||
)
|
||||
.split('\n')[0];
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('requestForUpdateRecord returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `PUT /v1/acl/update?dc=${dc}`;
|
||||
const actual = adapter
|
||||
.requestForUpdateRecord(
|
||||
request,
|
||||
{},
|
||||
{
|
||||
Datacenter: dc,
|
||||
ID: id,
|
||||
}
|
||||
)
|
||||
.split('\n')[0];
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('requestForDeleteRecord returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `PUT /v1/acl/destroy/${id}?dc=${dc}`;
|
||||
const actual = adapter
|
||||
.requestForDeleteRecord(
|
||||
request,
|
||||
{},
|
||||
{
|
||||
Datacenter: dc,
|
||||
ID: id,
|
||||
}
|
||||
)
|
||||
.split('/n')[0];
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('requestForCloneRecord returns the correct url', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:acl');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const request = client.url.bind(client);
|
||||
const expected = `PUT /v1/acl/clone/${id}?dc=${dc}`;
|
||||
const actual = adapter
|
||||
.requestForCloneRecord(
|
||||
request,
|
||||
{},
|
||||
{
|
||||
Datacenter: dc,
|
||||
ID: id,
|
||||
}
|
||||
)
|
||||
.split('\n')[0];
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Adapter | acl', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let adapter = this.owner.lookup('adapter:acl');
|
||||
assert.ok(adapter);
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Controller | dc/acls/create', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.owner.lookup('controller:dc/acls/create');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Controller | dc/acls/edit', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let controller = this.owner.lookup('controller:dc/acls/edit');
|
||||
assert.ok(controller);
|
||||
});
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
import { module } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import test from 'ember-sinon-qunit/test-support/test';
|
||||
import Service from '@ember/service';
|
||||
import Route from 'consul-ui/routes/dc/acls/index';
|
||||
|
||||
import Mixin from 'consul-ui/mixins/acl/with-actions';
|
||||
|
||||
module('Unit | Mixin | acl/with actions', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
this.subject = function() {
|
||||
const MixedIn = Route.extend(Mixin);
|
||||
this.owner.register('test-container:acl/with-actions-object', MixedIn);
|
||||
return this.owner.lookup('test-container:acl/with-actions-object');
|
||||
};
|
||||
});
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it works', function(assert) {
|
||||
const subject = this.subject();
|
||||
assert.ok(subject);
|
||||
});
|
||||
test('use persists the token', function(assert) {
|
||||
assert.expect(2);
|
||||
const item = { ID: 'id' };
|
||||
const expected = { Namespace: 'default', AccessorID: null, SecretID: item.ID };
|
||||
this.owner.register(
|
||||
'service:settings',
|
||||
Service.extend({
|
||||
persist: function(actual) {
|
||||
assert.deepEqual(actual.token, expected);
|
||||
return Promise.resolve(actual);
|
||||
},
|
||||
})
|
||||
);
|
||||
const subject = this.subject();
|
||||
return subject.actions.use
|
||||
.bind(subject)(item)
|
||||
.then(function(actual) {
|
||||
assert.deepEqual(actual.token, expected);
|
||||
});
|
||||
});
|
||||
test('clone clones the token and calls afterDelete correctly', function(assert) {
|
||||
assert.expect(4);
|
||||
this.owner.register(
|
||||
'service:feedback',
|
||||
Service.extend({
|
||||
execute: function(cb, name) {
|
||||
assert.equal(name, 'clone');
|
||||
return cb();
|
||||
},
|
||||
})
|
||||
);
|
||||
const expected = { ID: 'id' };
|
||||
this.owner.register(
|
||||
'service:repository/acl',
|
||||
Service.extend({
|
||||
clone: function(actual) {
|
||||
assert.deepEqual(actual, expected);
|
||||
return Promise.resolve(actual);
|
||||
},
|
||||
})
|
||||
);
|
||||
const subject = this.subject();
|
||||
const afterDelete = this.stub(subject, 'afterDelete').returnsArg(0);
|
||||
return subject.actions.clone
|
||||
.bind(subject)(expected)
|
||||
.then(function(actual) {
|
||||
assert.ok(afterDelete.calledOnce);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module('Unit | Model | acl', 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('acl', {}));
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | dc/acls', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:dc/acls');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | dc/acls/create', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:dc/acls/create');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | dc/acls/edit', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:dc/acls/edit');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | dc/acls/index', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:dc/acls/index');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
|
||||
import ExactSearch from 'consul-ui/utils/search/exact';
|
||||
import predicates from 'consul-ui/search/predicates/acl';
|
||||
|
||||
module('Unit | Search | Predicate | acl', function() {
|
||||
test('items are found by properties', function(assert) {
|
||||
const actual = new ExactSearch(
|
||||
[
|
||||
{
|
||||
ID: 'HIT-id',
|
||||
Name: 'name',
|
||||
},
|
||||
{
|
||||
ID: 'id',
|
||||
Name: 'name',
|
||||
},
|
||||
{
|
||||
ID: 'id',
|
||||
Name: 'name-HIT',
|
||||
},
|
||||
],
|
||||
{
|
||||
finders: predicates,
|
||||
}
|
||||
).search('hit');
|
||||
assert.equal(actual.length, 2);
|
||||
});
|
||||
test('items are not found', function(assert) {
|
||||
const actual = new ExactSearch(
|
||||
[
|
||||
{
|
||||
ID: 'id',
|
||||
Name: 'name',
|
||||
},
|
||||
],
|
||||
{
|
||||
finders: predicates,
|
||||
}
|
||||
).search('hit');
|
||||
assert.equal(actual.length, 0);
|
||||
});
|
||||
});
|
|
@ -1,24 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module('Unit | Serializer | acl', 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('acl');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = run(() => store.createRecord('acl', {}));
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
|
@ -1,12 +0,0 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | acl', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:repository/acl');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
import { module } from 'qunit';
|
||||
import test from 'ember-sinon-qunit/test-support/test';
|
||||
import aclsStatus from 'consul-ui/utils/acls-status';
|
||||
|
||||
module('Unit | Utility | acls status', function() {
|
||||
test('it rejects and nothing is enabled or authorized', function(assert) {
|
||||
const isValidServerError = this.stub().returns(false);
|
||||
const status = aclsStatus(isValidServerError);
|
||||
[
|
||||
this.stub().rejects(),
|
||||
this.stub().rejects({ errors: [] }),
|
||||
this.stub().rejects({ errors: [{ status: '404' }] }),
|
||||
].forEach(function(reject) {
|
||||
const actual = status({
|
||||
response: reject(),
|
||||
});
|
||||
assert.rejects(actual.response);
|
||||
['isAuthorized', 'isEnabled'].forEach(function(prop) {
|
||||
actual[prop].then(function(actual) {
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
test('with a 401 it resolves with an empty array and nothing is enabled or authorized', function(assert) {
|
||||
assert.expect(3);
|
||||
const isValidServerError = this.stub().returns(false);
|
||||
const status = aclsStatus(isValidServerError);
|
||||
const actual = status({
|
||||
response: this.stub().rejects({ errors: [{ status: '401' }] })(),
|
||||
});
|
||||
actual.response.then(function(actual) {
|
||||
assert.deepEqual(actual, []);
|
||||
});
|
||||
['isAuthorized', 'isEnabled'].forEach(function(prop) {
|
||||
actual[prop].then(function(actual) {
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
||||
test("with a 403 it resolves with an empty array and it's enabled but not authorized", function(assert) {
|
||||
assert.expect(3);
|
||||
const isValidServerError = this.stub().returns(false);
|
||||
const status = aclsStatus(isValidServerError);
|
||||
const actual = status({
|
||||
response: this.stub().rejects({ errors: [{ status: '403' }] })(),
|
||||
});
|
||||
actual.response.then(function(actual) {
|
||||
assert.deepEqual(actual, []);
|
||||
});
|
||||
actual.isEnabled.then(function(actual) {
|
||||
assert.ok(actual);
|
||||
});
|
||||
actual.isAuthorized.then(function(actual) {
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
test("with a 500 (but not a 'valid' error) it rejects and nothing is enabled or authorized", function(assert) {
|
||||
assert.expect(3);
|
||||
const isValidServerError = this.stub().returns(false);
|
||||
const status = aclsStatus(isValidServerError);
|
||||
const actual = status({
|
||||
response: this.stub().rejects({ errors: [{ status: '500' }] })(),
|
||||
});
|
||||
assert.rejects(actual.response);
|
||||
['isAuthorized', 'isEnabled'].forEach(function(prop) {
|
||||
actual[prop].then(function(actual) {
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
||||
test("with a 500 and a 'valid' error, it resolves with an empty array and it's enabled but not authorized", function(assert) {
|
||||
assert.expect(3);
|
||||
const isValidServerError = this.stub().returns(true);
|
||||
const status = aclsStatus(isValidServerError);
|
||||
const actual = status({
|
||||
response: this.stub().rejects({ errors: [{ status: '500' }] })(),
|
||||
});
|
||||
actual.response.then(function(actual) {
|
||||
assert.deepEqual(actual, []);
|
||||
});
|
||||
actual.isEnabled.then(function(actual) {
|
||||
assert.ok(actual);
|
||||
});
|
||||
actual.isAuthorized.then(function(actual) {
|
||||
assert.notOk(actual);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import createIsValidServerError from 'consul-ui/utils/http/acl/is-valid-server-error';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | http/acl/is valid server error', function() {
|
||||
const createEmberDataError = function(response) {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
detail: response,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
test('it returns a function', function(assert) {
|
||||
const isValidServerError = createIsValidServerError();
|
||||
assert.ok(typeof isValidServerError === 'function');
|
||||
});
|
||||
test("it returns false if there is no 'correctly' formatted error", function(assert) {
|
||||
const isValidServerError = createIsValidServerError();
|
||||
assert.notOk(isValidServerError());
|
||||
assert.notOk(isValidServerError({}));
|
||||
assert.notOk(isValidServerError({ errors: {} }));
|
||||
assert.notOk(isValidServerError({ errors: [{}] }));
|
||||
assert.notOk(isValidServerError({ errors: [{ notDetail: '' }] }));
|
||||
});
|
||||
// don't go too crazy with these, just enough for sanity check, we are essentially testing indexOf
|
||||
test("it returns false if the response doesn't contain the exact error response", function(assert) {
|
||||
const isValidServerError = createIsValidServerError();
|
||||
[
|
||||
"pc error making call: rpc: can't find method ACL",
|
||||
"rpc error making call: rpc: can't find method",
|
||||
"rpc rror making call: rpc: can't find method ACL",
|
||||
].forEach(function(response) {
|
||||
const e = createEmberDataError(response);
|
||||
assert.notOk(isValidServerError(e));
|
||||
});
|
||||
});
|
||||
test('it returns true if the response contains the exact error response', function(assert) {
|
||||
const isValidServerError = createIsValidServerError();
|
||||
[
|
||||
"rpc error making call: rpc: can't find method ACL",
|
||||
" rpc error making call: rpc: can't find method ACL",
|
||||
"rpc error making call: rpc: rpc error making call: rpc: rpc error making call: rpc: can't find method ACL",
|
||||
].forEach(function(response) {
|
||||
const e = createEmberDataError(response);
|
||||
assert.ok(isValidServerError(e));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
(function(appNameJS = 'consulUi', doc = document) {
|
||||
const scripts = doc.getElementsByTagName('script');
|
||||
const script = scripts[scripts.length - 1];
|
||||
script.dataset[`${appNameJS}Routes`] = JSON.stringify({
|
||||
dc: {
|
||||
acls: {
|
||||
tokens: {
|
||||
_options: {
|
||||
abilities: ['read tokens'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
})();
|
|
@ -5102,6 +5102,11 @@ deep-is@^0.1.3, deep-is@~0.1.3:
|
|||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
|
||||
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
|
||||
|
||||
deepmerge@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||
|
||||
defaults@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
|
||||
|
|
Loading…
Reference in New Issue