mirror of https://github.com/status-im/consul.git
ui: Partitions Application Layer (#11017)
* Add Partition to all our models * Add partitions into our serializers/fingerprinting * Make some amends to a few adapters ready for partitions * Amend blueprints to avoid linting error * Update all our repositories to include partitions, also Remove enabled/disable nspace repo and just use a nspace with conditionals * Ensure nspace and parition parameters always return '' no matter what * Ensure data-sink finds the model properly This will later be replaced by a @dataSink decorator but we are find kicking that can down the road a little more * Add all the new partition data layer * Add a way to set the title of the page from inside the route and make it accessibile via a route announcer * Make the Consul Route the default/basic one * Tweak nspace and partition abilities not to check the length * Thread partition through all the components that need it * Some ACL tweaks * Move the entire app to use partitions * Delete all the tests we no longer need * Update some Unit tests to use partition * Fix up KV title tests * Fix up a few more acceptance tests * Fixup and temporarily ignore some acceptance tests * Stop using ember-cli-page-objects fillable as it doesn't seem to work * Fix lint error * Remove old ACL related test * Add a tick after filling out forms * Fix token warning modal * Found some more places where we need a partition var * Fixup some more acceptance tests * Tokens still needs a repo service for CRUD * Remove acceptance tests we no longer need * Fixup and "FIXME ignore" a few tests * Remove an s * Disable blocking queries for KV to revert to previous release for now * Fixup adapter tests to follow async/function resolving interface * Fixup all the serializer integration tests * Fixup service/repo integration tests * Fixup deleting acceptance test * Fixup some ent tests * Make sure nspaces passes the dc through for when thats important * ...aaaand acceptance nspaces with the extra dc param
This commit is contained in:
parent
0eb4a98fab
commit
fc14a412fd
|
@ -16,7 +16,7 @@ export default class NspaceAbility extends BaseAbility {
|
|||
}
|
||||
|
||||
get canChoose() {
|
||||
return this.canUse && (this.nspaces || []).length > 0;
|
||||
return this.canUse;
|
||||
}
|
||||
|
||||
get canUse() {
|
||||
|
|
|
@ -16,7 +16,7 @@ export default class PartitionAbility extends BaseAbility {
|
|||
}
|
||||
|
||||
get canChoose() {
|
||||
return this.canUse && (this.partitions || []).length > 0;
|
||||
return this.canUse;
|
||||
}
|
||||
|
||||
get canUse() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Adapter from './application';
|
||||
|
||||
export default class BindingRuleAdapter extends Adapter {
|
||||
requestForQuery(request, { dc, ns, partition, authmethod, index, id }) {
|
||||
requestForQuery(request, { dc, ns, partition, authmethod, index }) {
|
||||
return request`
|
||||
GET /v1/acl/binding-rules?${{ dc, authmethod }}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ export default class IntentionAdapter extends Adapter {
|
|||
}
|
||||
|
||||
${{
|
||||
partition: '',
|
||||
ns: '*',
|
||||
index,
|
||||
filter,
|
||||
|
|
|
@ -7,11 +7,11 @@ import { SLUG_KEY } from 'consul-ui/models/kv';
|
|||
const API_KEYS_KEY = 'keys';
|
||||
|
||||
export default class KvAdapter extends Adapter {
|
||||
requestForQuery(request, { dc, ns, partition, index, id, separator }) {
|
||||
async requestForQuery(request, { dc, ns, partition, index, id, separator }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
const respond = await request`
|
||||
GET /v1/kv/${keyToArray(id)}?${{ [API_KEYS_KEY]: null, dc, separator }}
|
||||
|
||||
${{
|
||||
|
@ -20,9 +20,11 @@ export default class KvAdapter extends Adapter {
|
|||
index,
|
||||
}}
|
||||
`;
|
||||
await respond((headers, body) => delete headers['x-consul-index']);
|
||||
return respond;
|
||||
}
|
||||
|
||||
requestForQueryRecord(request, { dc, ns, partition, index, id }) {
|
||||
async requestForQueryRecord(request, { dc, ns, partition, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ export default class NodeAdapter extends Adapter {
|
|||
`;
|
||||
}
|
||||
|
||||
// this does not require a partition parameter
|
||||
requestForQueryLeader(request, { dc, uri }) {
|
||||
return request`
|
||||
GET /v1/status/leader?${{ dc }}
|
||||
|
|
|
@ -3,9 +3,9 @@ import { SLUG_KEY } from 'consul-ui/models/nspace';
|
|||
|
||||
// namespaces aren't categorized by datacenter, therefore no dc
|
||||
export default class NspaceAdapter extends Adapter {
|
||||
requestForQuery(request, { partition, index, uri }) {
|
||||
requestForQuery(request, { dc, partition, index, uri }) {
|
||||
return request`
|
||||
GET /v1/namespaces
|
||||
GET /v1/namespaces?${{ dc }}
|
||||
X-Request-ID: ${uri}
|
||||
|
||||
${{
|
||||
|
@ -15,12 +15,12 @@ export default class NspaceAdapter extends Adapter {
|
|||
`;
|
||||
}
|
||||
|
||||
requestForQueryRecord(request, { partition, index, id }) {
|
||||
requestForQueryRecord(request, { dc, partition, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an name');
|
||||
}
|
||||
return request`
|
||||
GET /v1/namespace/${id}
|
||||
GET /v1/namespace/${id}?${{ dc }}
|
||||
|
||||
${{
|
||||
partition,
|
||||
|
@ -32,6 +32,7 @@ export default class NspaceAdapter extends Adapter {
|
|||
requestForCreateRecord(request, serialized, data) {
|
||||
return request`
|
||||
PUT /v1/namespace/${data[SLUG_KEY]}?${{
|
||||
dc: data.Datacenter,
|
||||
partition: data.Partition,
|
||||
}}
|
||||
|
||||
|
@ -49,6 +50,7 @@ export default class NspaceAdapter extends Adapter {
|
|||
requestForUpdateRecord(request, serialized, data) {
|
||||
return request`
|
||||
PUT /v1/namespace/${data[SLUG_KEY]}?${{
|
||||
dc: data.Datacenter,
|
||||
partition: data.Partition,
|
||||
}}
|
||||
|
||||
|
@ -65,6 +67,7 @@ export default class NspaceAdapter extends Adapter {
|
|||
requestForDeleteRecord(request, serialized, data) {
|
||||
return request`
|
||||
DELETE /v1/namespace/${data[SLUG_KEY]}?${{
|
||||
dc: data.Datacenter,
|
||||
partition: data.Partition,
|
||||
}}
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import Adapter from './application';
|
||||
|
||||
// Blocking query support for partitions is currently disabled
|
||||
export default class PartitionAdapter extends Adapter {
|
||||
// FIXME: Check overall hierarchy again
|
||||
async requestForQuery(request, { ns, dc, index }) {
|
||||
const respond = await request`
|
||||
GET /v1/partitions?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
await respond((headers, body) => delete headers['x-consul-index']);
|
||||
return respond;
|
||||
}
|
||||
|
||||
async requestForQueryRecord(request, { ns, dc, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
const respond = await request`
|
||||
GET /v1/partition/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
await respond((headers, body) => delete headers['x-consul-index']);
|
||||
return respond;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ export default class PermissionAdapter extends Adapter {
|
|||
resources = resources.map(item => ({ ...item, Partition: partition }));
|
||||
}
|
||||
return request`
|
||||
POST /v1/internal/acl/authorize?${{ dc, index }}
|
||||
POST /v1/internal/acl/authorize?${{ dc }}
|
||||
|
||||
${resources}
|
||||
`;
|
||||
|
|
|
@ -17,12 +17,13 @@ export default class TokenAdapter extends Adapter {
|
|||
`;
|
||||
}
|
||||
|
||||
requestForQueryRecord(request, { dc, ns, partition, index, id }) {
|
||||
async requestForQueryRecord(request, { dc, ns, partition, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
const respond = await request`
|
||||
GET /v1/acl/token/${id}?${{ dc }}
|
||||
Cache-Control: no-store
|
||||
|
||||
${{
|
||||
ns,
|
||||
|
@ -30,6 +31,8 @@ export default class TokenAdapter extends Adapter {
|
|||
index,
|
||||
}}
|
||||
`;
|
||||
respond((headers, body) => delete headers['x-consul-index']);
|
||||
return respond;
|
||||
}
|
||||
|
||||
requestForCreateRecord(request, serialized, data) {
|
||||
|
|
|
@ -22,7 +22,11 @@
|
|||
token=token
|
||||
) (hash
|
||||
AuthProfile=(component 'auth-profile' item=token)
|
||||
AuthForm=(component 'auth-form' dc=dc nspace=nspace onsubmit=(action sink.open value="data"))
|
||||
AuthForm=(component 'auth-form'
|
||||
dc=dc
|
||||
partition=partition
|
||||
nspace=nspace
|
||||
onsubmit=(action sink.open value="data"))
|
||||
) as |api components|}}
|
||||
<State @matches="authorized">
|
||||
{{#yield-slot name="authorized"}}
|
||||
|
|
|
@ -77,7 +77,13 @@
|
|||
{{#if (env 'CONSUL_SSO_ENABLED')}}
|
||||
{{!-- This `or` can be completely removed post 1.10 as 1.10 has optional params with default values --}}
|
||||
<DataSource
|
||||
@src={{concat '/' (or nspace '') '/' dc '/oidc/providers'}}
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/oidc/providers'
|
||||
(hash
|
||||
partition=partition
|
||||
nspace=(or nspace '')
|
||||
dc=dc
|
||||
)
|
||||
}}
|
||||
@onchange={{queue (action (mut providers) value="data")}}
|
||||
@onerror={{queue (action (mut error) value="error.errors.firstObject")}}
|
||||
@loading="lazy"
|
||||
|
@ -96,9 +102,11 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
<State @matches="loading">
|
||||
{{!FIXME: default partition?}}
|
||||
<TokenSource
|
||||
@dc={{dc}}
|
||||
@nspace={{or value.Namespace nspace}}
|
||||
@partition={{or value.Partition 'default'}}
|
||||
@type={{if value.Name 'oidc' 'secret'}}
|
||||
@value={{if value.Name value.Name value}}
|
||||
@onchange={{queue (action dispatch "RESET") (action onsubmit)}}
|
||||
|
|
|
@ -9,7 +9,14 @@
|
|||
<span><YieldSlot @name="label">{{yield}}</YieldSlot></span>
|
||||
{{#if isOpen}}
|
||||
<DataSource
|
||||
@src={{concat '/' nspace '/' dc '/' (pluralize type)}}
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/${type}'
|
||||
(hash
|
||||
partition=partition
|
||||
nspace=nspace
|
||||
dc=dc
|
||||
type=(pluralize type)
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut allOptions) value="data"}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<AppView>
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
Tokens
|
||||
</h1>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
<EmptyState data-test-acls-disabled>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to ACLs</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
ACLs are not enabled in this Consul cluster. We strongly encourage the use of ACLs in production environments for the best security practices.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions">
|
||||
<li class="docs-link">
|
||||
<a href="{{env 'CONSUL_DOCS_URL'}}/acl/index.html" rel="noopener noreferrer" target="_blank">Read the documentation</a>
|
||||
</li>
|
||||
<li class="learn-link">
|
||||
<a href="{{env 'CONSUL_DOCS_LEARN_URL'}}/consul/security-networking/production-acls" rel="noopener noreferrer" target="_blank">Follow the guide</a>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
|
|
@ -6,7 +6,7 @@ A presentational component for rendering HealthChecks.
|
|||
<figure>
|
||||
<figcaption>Grab some mock data...</figcaption>
|
||||
|
||||
<DataSource @src="/default/dc-1/node/my-node" as |source|>
|
||||
<DataSource @src="/partition/default/dc-1/node/my-node" as |source|>
|
||||
<figure>
|
||||
<figcaption>but only show a max of 2 items for docs purposes</figcaption>
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@type="intention"
|
||||
@dc={{@dc}}
|
||||
@nspace={{@nspace}}
|
||||
@partition={{@partition}}
|
||||
@autofill={{@autofill}}
|
||||
@item={{@item}}
|
||||
@src={{@src}}
|
||||
|
@ -73,15 +74,27 @@ as |api|>
|
|||
{{/let}}
|
||||
|
||||
<DataSource
|
||||
@src={{concat '/*/' @dc '/services'}}
|
||||
@src={{uri '/${partition}/*/${dc}/services'
|
||||
(hash
|
||||
partition=@partition
|
||||
dc=@dc
|
||||
)
|
||||
}}
|
||||
@onchange={{action this.createServices item}}
|
||||
/>
|
||||
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
|
||||
|
||||
{{#if (can 'use nspaces')}}
|
||||
<DataSource
|
||||
@src="/*/*/namespaces"
|
||||
@src={{uri '/${partition}/*/${dc}/namespaces'
|
||||
(hash
|
||||
partition=@partition
|
||||
dc=@dc
|
||||
)
|
||||
}}
|
||||
@onchange={{action this.createNspaces item}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and api.isCreate this.isManagedByCRDs)}}
|
||||
<Consul::Intention::Notice::CustomResource @type="warning" />
|
||||
{{/if}}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<DataForm
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@type="kv"
|
||||
@label="key"
|
||||
@autofill={{autofill}}
|
||||
|
@ -19,11 +20,11 @@
|
|||
{{#if api.isCreate}}
|
||||
<label class="type-text{{if api.data.error.Key ' has-error'}}">
|
||||
<span>Key or folder</span>
|
||||
<input autofocus="autofocus" type="text" value={{left-trim api.data.Key parent.Key}} name="additional" oninput={{action api.change}} placeholder="Key or folder" />
|
||||
<input autofocus="autofocus" type="text" value={{left-trim api.data.Key parent}} name="additional" oninput={{action api.change}} placeholder="Key or folder" />
|
||||
<em>To create a folder, end a key with <code>/</code></em>
|
||||
</label>
|
||||
{{/if}}
|
||||
{{#if (or (eq (left-trim api.data.Key parent.Key) '') (not-eq (last api.data.Key) '/'))}}
|
||||
{{#if (or (eq (left-trim api.data.Key parent) '') (not-eq (last api.data.Key) '/'))}}
|
||||
<div>
|
||||
<div class="type-toggle">
|
||||
<label>
|
||||
|
|
|
@ -26,7 +26,7 @@ export default Component.extend({
|
|||
set(item, 'Value', this.encoder.execute(target.value));
|
||||
break;
|
||||
case 'additional':
|
||||
parent = get(this, 'parent.Key');
|
||||
parent = get(this, 'parent');
|
||||
set(item, 'Key', `${parent !== '/' ? parent : ''}${target.value}`);
|
||||
break;
|
||||
case 'json':
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<DataForm
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@item={{item}}
|
||||
@type="session"
|
||||
@onsubmit={{action onsubmit}}
|
||||
|
|
|
@ -4,7 +4,7 @@ class: ember
|
|||
# Consul::LockSession::List
|
||||
|
||||
```hbs preview-template
|
||||
<DataSource @src="/default/dc-1/sessions/for-node/my-node" as |source|>
|
||||
<DataSource @src="/partition/default/dc-1/sessions/for-node/my-node" as |source|>
|
||||
<Consul::LockSession::List
|
||||
@items={{source.data}}
|
||||
@onInvalidate={{action (noop)}}
|
||||
|
|
|
@ -8,7 +8,7 @@ A presentational component for presenting Consul Metadata
|
|||
The following example shows how to construct the required structure from the Consul API using ember-componsable-helpers' `entries` helper.
|
||||
|
||||
```hbs
|
||||
<DataSource @src="/default/dc-1/service-instance/service-id/node-0/service-0" as |source|>
|
||||
<DataSource @src="/partition/default/dc-1/service-instance/service-id/node-0/service-0" as |source|>
|
||||
<Consul::Metadata::List
|
||||
@items={{entries source.data.firstObject.Meta}}
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@ class: ember
|
|||
## Consul::Nspace::List
|
||||
|
||||
```hbs
|
||||
<DataSource @src="/default/dc-1/namespaces" as |source|>
|
||||
<DataSource @src="/partition/default/dc-1/namespaces" as |source|>
|
||||
<Consul::Nspace::List
|
||||
@items={{source.data}}
|
||||
@ondelete={{action (noop)}}
|
||||
|
|
|
@ -81,7 +81,15 @@ as |key value|}}
|
|||
{{#each dcs as |dc|}}
|
||||
<Option @value={{dc.Name}} @selected={{contains dc.Name @filter.datacenter.value}}>{{dc.Name}}</Option>
|
||||
{{/each}}
|
||||
<DataSource @src="/*/*/datacenters" @loading="lazy" @onchange={{action (mut this.dcs) value="data"}} />
|
||||
<DataSource
|
||||
@src={{uri "/${partition}/*/*/datacenters"
|
||||
(hash
|
||||
partition=@partition
|
||||
)
|
||||
}}
|
||||
@loading="lazy"
|
||||
@onchange={{action (mut this.dcs) value="data"}}
|
||||
/>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</search.Select>
|
||||
|
|
|
@ -19,7 +19,7 @@ Lastly, a `SearchService` in `services/search.js` configures what is available f
|
|||
|
||||
<figure>
|
||||
<figcaption>Get some data to search on</figcaption>
|
||||
<DataSource @src="/nspace/dc-1/services" as |source|>
|
||||
<DataSource @src="/partition/nspace/dc-1/services" as |source|>
|
||||
|
||||
<figure>
|
||||
<figcaption>and show the complete set of data</figcaption>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<DataLoader
|
||||
@items={{item}}
|
||||
@src={{uri
|
||||
'/${nspace}/${dc}/${type}/${src}'
|
||||
'/${partition}/${nspace}/${dc}/${type}/${src}'
|
||||
(hash
|
||||
partition=partition
|
||||
nspace=nspace
|
||||
dc=dc
|
||||
type=type
|
||||
|
@ -16,8 +17,9 @@
|
|||
|
||||
<DataWriter
|
||||
@sink={{uri
|
||||
'/${nspace}/${dc}/${type}'
|
||||
'/${partition}/${nspace}/${dc}/${type}'
|
||||
(hash
|
||||
partition=partition
|
||||
nspace=nspace
|
||||
dc=(or data.Datacenter dc)
|
||||
type=type
|
||||
|
|
|
@ -46,7 +46,7 @@ export default Component.extend(Slotted, {
|
|||
}
|
||||
// mark as creating
|
||||
// and autofill the new record if required
|
||||
if (get(changeset, 'isNew')) {
|
||||
if (get(data, 'isNew')) {
|
||||
set(this, 'create', true);
|
||||
changeset = Object.entries(this.autofill || {}).reduce(function(prev, [key, value]) {
|
||||
set(prev, key, value);
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# DataLoader
|
||||
|
||||
`<DataLoader />` works similarly to, and uses, `<DataSource />` but additionally
|
||||
exposes various common states based on the status of the loading of the data.
|
||||
These states are exposed as slots to enable you to easily render different
|
||||
elements based on the state of the data.
|
||||
|
||||
|
||||
Use the `@dataSource` decorator in your repositories to define URI to async
|
||||
method mapping.
|
||||
|
||||
```javascript
|
||||
class SomethingRepository extends Service {
|
||||
@dataSource('/:partition/:nspace/:dc/services')
|
||||
async youCouldCallItAnythingTodoWithGettingServices(params) {
|
||||
console.log(params);
|
||||
// {partition: "partition", nspace: "nspace", dc: "dc"}
|
||||
return getTheThing(params);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```hbs preview-template
|
||||
<DataLoader
|
||||
@src="/partition/nspace/dc/services"
|
||||
as |loader|>
|
||||
<BlockSlot @name="loading">
|
||||
Loading...
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="error">
|
||||
Error {{loader.error.status}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="disconnected">
|
||||
Whilst we could load the initial data, something happened subsequently that
|
||||
meant we could load longer load updates to the data.
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="loaded">
|
||||
{{#each loader.data as |service|}}
|
||||
{{service.Name}}<br />
|
||||
{{/each}}
|
||||
</BlockSlot>
|
||||
</DataLoader>
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `src` | `String` | | The source to subscribe to updates to, this should map to a string based URI |
|
||||
|
||||
## Exports
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `data` | The loaded dataset once any data has been loaded successfully |
|
||||
| `error` | The error thrown if an error is encountered whilst loading data |
|
||||
|
||||
## Slots
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| `loading` | Rendered whilst waiting for the initial data to load. |
|
||||
| `error` | If there is an error only whilst waiting for the initial data to load, this slot is rendered. |
|
||||
| `disconnected` | Rendered when the initial data has already loaded, but a subsequent set of loaded data causes an error to be thrown.|
|
||||
| `loaded` | Rendered once the initial data is loaded and on subsequent successful loads of data. |
|
||||
|
||||
## See
|
||||
|
||||
- [DataSource](../data-source/README.mdx)
|
||||
- [Component Source Code](./index.js)
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -2,6 +2,9 @@ export default {
|
|||
id: 'data-loader',
|
||||
initial: 'load',
|
||||
on: {
|
||||
OPEN: {
|
||||
target: 'load',
|
||||
},
|
||||
ERROR: {
|
||||
target: 'disconnected',
|
||||
},
|
||||
|
|
|
@ -54,12 +54,14 @@
|
|||
{{#yield-slot name="disconnected" params=(block-params (component 'notification' after=(action dispatch "RESET")))}}
|
||||
{{yield api}}
|
||||
{{else}}
|
||||
{{#if (not eq error.status '401')}}
|
||||
<Notification @sticky={{true}}>
|
||||
<p data-notification role="alert" class="warning notification-update">
|
||||
<strong>Warning!</strong>
|
||||
An error was returned whilst loading this data, refresh to try again.
|
||||
</p>
|
||||
</Notification>
|
||||
{{/if}}
|
||||
{{/yield-slot}}
|
||||
</State>
|
||||
{{#if (eq error.status "403")}}
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
## DataSource
|
||||
# DataSource
|
||||
|
||||
Use the `@dataSource` decorator in your repositories to define URI to async
|
||||
method mapping.
|
||||
|
||||
```javascript
|
||||
class SomethingRepository extends Service {
|
||||
@dataSource('/:partition/:nspace/:dc/services')
|
||||
async youCouldCallItAnythingTodoWithGettingServices(params) {
|
||||
console.log(params);
|
||||
// {partition: "partition", nspace: "nspace", dc: "dc"}
|
||||
return getTheThing(params);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```hbs preview-template
|
||||
<DataSource
|
||||
@src="/nspace/dc/services"
|
||||
@src="/partition/nspace/dc/services"
|
||||
@loading="eager"
|
||||
@disabled={{false}}
|
||||
as |source|>
|
||||
|
@ -12,7 +26,7 @@ as |source|>
|
|||
</DataSource>
|
||||
```
|
||||
|
||||
### Arguments
|
||||
## Attributes
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
|
@ -34,14 +48,14 @@ Behind the scenes in the Consul UI we map URIs back to our `ember-data` backed `
|
|||
`DataSource` is not just restricted to HTTP API data, and can be configured to listen for data changes using a variety of methods and sources. For example we have also configured `DataSource` to listen to `LocalStorage` changes using the `settings://` pseudo-protocol in the URI (See examples below).
|
||||
|
||||
|
||||
### Examples
|
||||
## Examples
|
||||
|
||||
Straightforward usage can use `mut` to easily update data within a template using an event handler approach.
|
||||
|
||||
```hbs
|
||||
{{! listen for HTTP API changes}}
|
||||
<DataSource
|
||||
@src="/nspace/dc/services"
|
||||
@src="/partition/nspace/dc/services"
|
||||
@onchange={{action (mut items) value="data"}}
|
||||
@onerror={{action (mut error) value="error"}}
|
||||
/>
|
||||
|
@ -71,7 +85,7 @@ A property approach to easily update data within a template
|
|||
```hbs
|
||||
{{! listen for HTTP API changes}}
|
||||
<DataSource
|
||||
@src="/nspace/dc/services"
|
||||
@src="/partition/nspace/dc/services"
|
||||
as |source|>
|
||||
{{#if source.error}}
|
||||
Something went wrong!
|
||||
|
@ -101,19 +115,19 @@ DataSources can also be recursively nested for loading in series as opposed to i
|
|||
|
||||
{{! listen for HTTP API changes}}
|
||||
<DataSource
|
||||
@src="/nspace/dc/services"
|
||||
@src="/partition/nspace/dc/services"
|
||||
@onerror={{action (mut error) value="error"}}
|
||||
as |source|>
|
||||
|
||||
<source.Source
|
||||
@src="/nspace/dc/service/{{source.data.firstObject.Name}}"
|
||||
@src="/partition/nspace/dc/service/{{source.data.firstObject.Name}}"
|
||||
@onerror={{action (mut error) value="error"}}
|
||||
as |source|>
|
||||
|
||||
{{source.data.Service.Service.Name}} <== Detailed information for the first service
|
||||
|
||||
<source.Source
|
||||
@src="/nspace/dc/proxy/for-service/{{source.data.Service.ID}}"
|
||||
@src="/partition/nspace/dc/proxy/for-service/{{source.data.Service.ID}}"
|
||||
@onerror={{action (mut error) value="error"}}
|
||||
@onchange={{action (mut loaded) true}}
|
||||
as |source|>
|
||||
|
@ -127,7 +141,7 @@ DataSources can also be recursively nested for loading in series as opposed to i
|
|||
</DataSource>
|
||||
```
|
||||
|
||||
### See
|
||||
## See
|
||||
|
||||
- [Component Source Code](./index.js)
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
|
|
@ -89,6 +89,14 @@ export default class DataSource extends Component {
|
|||
|
||||
@action
|
||||
disconnect() {
|
||||
// FIXME? Should we be doing this here
|
||||
if (
|
||||
typeof this.data !== 'undefined' &&
|
||||
typeof this.data.length === 'undefined' &&
|
||||
typeof this.data.rollbackAttributes === 'function'
|
||||
) {
|
||||
this.data.rollbackAttributes();
|
||||
}
|
||||
this.close();
|
||||
this._listeners.remove();
|
||||
this._lazyListeners.remove();
|
||||
|
@ -169,6 +177,15 @@ export default class DataSource extends Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
@action
|
||||
async invalidate() {
|
||||
this.source.readyState = 2;
|
||||
this.disconnect();
|
||||
schedule('afterRender', () => {
|
||||
// FIXME: Lazy data-sources
|
||||
this.connect([]);
|
||||
});
|
||||
}
|
||||
|
||||
// keep this argumentless
|
||||
@action
|
||||
|
|
|
@ -11,9 +11,7 @@
|
|||
</:home-nav>
|
||||
|
||||
<:main-nav>
|
||||
{{#if @dc}}
|
||||
<ul>
|
||||
|
||||
<li
|
||||
class="dcs"
|
||||
data-test-datacenter-menu
|
||||
|
@ -28,7 +26,7 @@
|
|||
<BlockSlot @name="menu">
|
||||
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
|
||||
<DataSource
|
||||
@src="/*/*/datacenters"
|
||||
@src="/*/*/*/datacenters"
|
||||
@onchange={{action (mut @dcs) value="data"}}
|
||||
@loading="lazy"
|
||||
/>
|
||||
|
@ -51,8 +49,61 @@
|
|||
</PopoverMenu>
|
||||
</li>
|
||||
|
||||
{{#let (or this.nspaces @nspaces) as |nspaces|}}
|
||||
{{#if (can "choose nspaces" nspaces=nspaces)}}
|
||||
{{#if (can "choose partitions")}}
|
||||
<li
|
||||
class="partitions"
|
||||
data-test-partition-menu
|
||||
>
|
||||
<PopoverMenu
|
||||
aria-label="Admin Partition"
|
||||
@position="left"
|
||||
as |components api|>
|
||||
<BlockSlot @name="trigger">
|
||||
{{@partition}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="menu">
|
||||
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
|
||||
<DataSource
|
||||
@src={{uri
|
||||
'/*/*/${dc}/partitions'
|
||||
(hash
|
||||
dc=@dc.Name
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut this.partitions) value="data"}}
|
||||
/>
|
||||
{{!FIXME: Do partitions do the same as namespace deletion? }}
|
||||
{{#each (reject-by 'DeletedAt' this.partitions) as |item|}}
|
||||
<MenuItem
|
||||
class={{if (eq @partition item.Name) 'is-active'}}
|
||||
@href={{href-to '.' params=(hash
|
||||
partition=item.Name
|
||||
nspace=(if (gt @nspace.length 0) @nspace undefined)
|
||||
)}}
|
||||
>
|
||||
<BlockSlot @name="label">
|
||||
{{item.Name}}
|
||||
</BlockSlot>
|
||||
</MenuItem>
|
||||
{{/each}}
|
||||
{{#if (and false (can 'manage partitions'))}}
|
||||
<MenuSeparator />
|
||||
<MenuItem
|
||||
data-test-main-nav-partitions
|
||||
@href={{href-to 'dc.nspaces' @dc.Name}}
|
||||
>
|
||||
<BlockSlot @name="label">
|
||||
Manage Admin Partitions
|
||||
</BlockSlot>
|
||||
</MenuItem>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</PopoverMenu>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can "choose nspaces")}}
|
||||
<li
|
||||
class="nspaces"
|
||||
data-test-nspace-menu
|
||||
|
@ -62,7 +113,7 @@
|
|||
@position="left"
|
||||
as |components api|>
|
||||
<BlockSlot @name="trigger">
|
||||
{{@nspace.Name}}
|
||||
{{@nspace}}
|
||||
</BlockSlot>
|
||||
{{#if (is-href 'dc.nspaces')}}
|
||||
<BlockSlot @name="header">
|
||||
|
@ -74,14 +125,23 @@
|
|||
<BlockSlot @name="menu">
|
||||
{{#let components.MenuItem components.MenuSeparator as |MenuItem MenuSeparator|}}
|
||||
<DataSource
|
||||
@src="/*/*/namespaces"
|
||||
@src={{uri
|
||||
'/${partition}/*/${dc}/namespaces'
|
||||
(hash
|
||||
partition=@partition
|
||||
dc=@dc.Name
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut this.nspaces) value="data"}}
|
||||
@loading="lazy"
|
||||
/>
|
||||
{{#each (reject-by 'DeletedAt' nspaces) as |item|}}
|
||||
{{#each (reject-by 'DeletedAt' this.nspaces) as |item|}}
|
||||
<MenuItem
|
||||
class={{if (eq @nspace.Name item.Name) 'is-active'}}
|
||||
@href={{href-to '.' params=(hash nspace=item.Name)}}
|
||||
class={{if (eq @nspace item.Name) 'is-active'}}
|
||||
@href={{href-to '.' params=(hash
|
||||
partition=(if (gt @partition.length 0) @partition undefined)
|
||||
nspace=item.Name
|
||||
)}}
|
||||
>
|
||||
<BlockSlot @name="label">
|
||||
{{item.Name}}
|
||||
|
@ -104,7 +164,6 @@
|
|||
</PopoverMenu>
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{#if (can "read services")}}
|
||||
<li data-test-main-nav-services class={{if (is-href 'dc.services' @dc.Name) 'is-active'}}>
|
||||
<a href={{href-to 'dc.services' @dc.Name}}>Services</a>
|
||||
|
@ -158,8 +217,6 @@
|
|||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
</:main-nav>
|
||||
|
||||
<:complementary-nav>
|
||||
|
@ -207,13 +264,17 @@
|
|||
</PopoverMenu>
|
||||
</li>
|
||||
<li data-test-main-nav-settings class={{if (is-href 'settings') 'is-active'}}>
|
||||
<a href={{href-to 'settings'}}>Settings</a>
|
||||
<a href={{href-to 'settings' params=(hash
|
||||
nspace=undefined
|
||||
partition=undefined
|
||||
)}}>Settings</a>
|
||||
</li>
|
||||
{{#if (can 'authenticate')}}
|
||||
<li data-test-main-nav-auth>
|
||||
<AuthDialog
|
||||
@dc={{@dc.Name}}
|
||||
@nspace={{@nspace.Name}}
|
||||
@nspace={{@nspace}}
|
||||
@partition={{@partition}}
|
||||
@onchange={{this.reauthorize}} as |authDialog components|
|
||||
>
|
||||
{{#let components.AuthForm components.AuthProfile as |AuthForm AuthProfile|}}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
%main-nav-vertical:not(.in-viewport) {
|
||||
visibility: hidden;
|
||||
}
|
||||
%main-nav-vertical li.partitions,
|
||||
%main-nav-vertical li.nspaces,
|
||||
%main-nav-vertical li.dcs {
|
||||
margin-bottom: 25px;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{{#each templates as |template|}}
|
||||
<label data-test-radiobutton={{concat 'template_' template.template}}>
|
||||
<span>{{template.name}}</span>
|
||||
<input type="radio" name={{concat name '[template]'}} value={{template.template}} checked={{eq item.template template.template}} onchange={{action (changeset-set item 'template') value='target.value'}}/>
|
||||
<input type="radio" name={{concat name '[template]'}} value={{template.template}} checked={{eq item.template template.template}} onchange={{action (optional (changeset-set item 'template')) value='target.value'}}/>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
@ -72,7 +72,8 @@
|
|||
</label>
|
||||
{{#if (eq item.template 'node-identity')}}
|
||||
|
||||
<DataSource @src="/*/*/datacenters"
|
||||
<DataSource
|
||||
@src={{uri '/*/*/*/datacenters'}}
|
||||
@onchange={{action (mut datacenters) value="data"}}
|
||||
/>
|
||||
<label class="type-select" data-test-datacenter>
|
||||
|
@ -95,7 +96,8 @@
|
|||
</label>
|
||||
</div>
|
||||
{{#if isScoped }}
|
||||
<DataSource @src="/*/*/datacenters"
|
||||
<DataSource
|
||||
@src={{uri '/*/*/*/datacenters'}}
|
||||
@onchange={{action (mut datacenters) value="data"}}
|
||||
/>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@disabled={{disabled}}
|
||||
@repo={{repo}}
|
||||
@dc={{dc}}
|
||||
@partition={{partition}}
|
||||
@nspace={{nspace}}
|
||||
@type="policy"
|
||||
@placeholder="Search for policy"
|
||||
|
@ -38,7 +39,13 @@
|
|||
<h2>New Policy</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<PolicyForm @form={{form}} @nspace={{nspace}} @dc={{dc}} @allowServiceIdentity={{allowServiceIdentity}} />
|
||||
<PolicyForm
|
||||
@form={{form}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@dc={{dc}}
|
||||
@allowServiceIdentity={{allowServiceIdentity}}
|
||||
/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions" as |close|>
|
||||
<button type="submit"
|
||||
|
@ -79,7 +86,14 @@
|
|||
<BlockSlot @name="details">
|
||||
{{#if (eq item.template '')}}
|
||||
<DataSource
|
||||
@src={{concat '/' item.Namespace '/' dc '/policy/' item.ID}}
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/policy/${id}'
|
||||
(hash
|
||||
partition=item.Partition
|
||||
nspace=item.Namespace
|
||||
dc=dc
|
||||
id=item.ID
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut loadedItem) value="data"}}
|
||||
@loading="lazy"
|
||||
/>
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<PolicySelector
|
||||
@disabled={{not (can "write role" item=item)}}
|
||||
@dc={{dc}}
|
||||
@partition={{partition}}
|
||||
@nspace={{nspace}}
|
||||
@items={{item.Policies}}
|
||||
/>
|
||||
|
|
|
@ -18,10 +18,21 @@ as |modal|>
|
|||
<BlockSlot @name="body">
|
||||
|
||||
<input id="{{name}}_state_role" type="radio" name="{{name}}[state]" value="role" checked={{if (eq state 'role') 'checked'}} onchange={{action 'change'}} />
|
||||
<RoleForm @form={{form}} @dc={{dc}} @nspace={{nspace}}>
|
||||
<RoleForm
|
||||
@form={{form}}
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
>
|
||||
<BlockSlot @name="policy">
|
||||
|
||||
<PolicySelector @source={{source}} @dc={{dc}} @nspace={{nspace}} @items={{item.Policies}}>
|
||||
<PolicySelector
|
||||
@source={{source}}
|
||||
@dc={{dc}}
|
||||
@partition={{partition}}
|
||||
@nspace={{nspace}}
|
||||
@items={{item.Policies}}
|
||||
>
|
||||
<BlockSlot @name="trigger">
|
||||
<label for="{{name}}_state_policy" data-test-create-policy class="type-dialog">
|
||||
<span>Create new policy</span>
|
||||
|
@ -33,7 +44,14 @@ as |modal|>
|
|||
</RoleForm>
|
||||
|
||||
<input id="{{name}}_state_policy" type="radio" name="{{name}}[state]" value="policy" checked={{if (eq state 'policy') 'checked'}} onchange={{action 'change'}} />
|
||||
<PolicyForm data-test-policy-form @name="role[policy]" @form={{policyForm}} @dc={{dc}} />
|
||||
<PolicyForm
|
||||
data-test-policy-form
|
||||
@name="role[policy]"
|
||||
@form={{policyForm}}
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
/>
|
||||
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="actions" as |close|>
|
||||
|
@ -62,7 +80,16 @@ as |modal|>
|
|||
</BlockSlot>
|
||||
</ModalDialog>
|
||||
|
||||
<ChildSelector @disabled={{disabled}} @repo={{repo}} @dc={{dc}} @nspace={{nspace}} @type="role" @placeholder="Search for role" @items={{items}}>
|
||||
<ChildSelector
|
||||
@disabled={{disabled}}
|
||||
@repo={{repo}}
|
||||
@dc={{dc}}
|
||||
@partition={{partition}}
|
||||
@nspace={{nspace}}
|
||||
@type="role"
|
||||
@placeholder="Search for role"
|
||||
@items={{items}}
|
||||
>
|
||||
<BlockSlot @name="label">
|
||||
Apply an existing role
|
||||
</BlockSlot>
|
||||
|
|
|
@ -12,8 +12,8 @@ routes.
|
|||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `String` | `undefined` | The name of the route in ember routeName format e.g. `dc.services.index`. This is generally the `routeName` variable that is available to you in all Consul UI route/page level templates.|
|
||||
| `title` | `String` | `undefined` | The title for this page (eventually passed through to the `{{page-title}}` helper. This argument should be omitted if a title change isn't required. |
|
||||
| `titleSeparator` | `String` | `undefined` | This can be used in the top-level route to configure the separator for the `{{page-title}}` helper |
|
||||
| `title` | `String` | `undefined` | Deprecated: The title for this page (eventually passed through to the `{{page-title}}` helper. This argument should be omitted if a title change isn't required. Also see the exported `<Title />` component which is now preferred |
|
||||
| `titleSeparator` | `String` | `undefined` | Deprecated: This can be used in the top-level route to configure the separator for the `{{page-title}}` helper. Also see the exported `<Title />` component which is now preferred |
|
||||
|
||||
## Exports
|
||||
|
||||
|
@ -21,16 +21,28 @@ routes.
|
|||
| --- | --- | --- | --- |
|
||||
| `model` | `Object` | `undefined` | Arbitrary hash of data passed down from the parent route/outlet |
|
||||
| `params` | `Object` | `undefined` | An object/merge of **all** optional route params and normal route params |
|
||||
| `Title` | `Component` | `` | An inline component to allow you to set a title within the Route component |
|
||||
| `Announcer` | `Component` | `` | An inline component to allow you to specify where the route announcer is rendered. This should be at the very top of your app probably under your `<Route />` in `application.hbs` |
|
||||
|
||||
```hbs
|
||||
<!-- application.hbs -->
|
||||
<Route
|
||||
@name={{routeName}}
|
||||
@title="Page Title"
|
||||
@titleSeparator=" - "
|
||||
as |route|>
|
||||
<route.Announcer />
|
||||
...
|
||||
</Route>
|
||||
|
||||
<!-- All route templates that change the title -->
|
||||
<Route
|
||||
@name={{routeName}}
|
||||
as |route|>
|
||||
<h1><route.Title @title="Page Title" /></h1>
|
||||
{{route.model.dc.Name}}
|
||||
</Route>
|
||||
```
|
||||
|
||||
Every page/route template has a `routeName` variable exposed specifically to
|
||||
allow you to use this to set the `@name` of the route.
|
||||
|
||||
The `<Title @title=""/>` component should be used to control the title of the page. This component also yields the value of the `@title` attribute allowing you to use it to avoid repeating the title of the page for things like reusing the same value for a `<h1>`. The `Title` component is one of the only components which uses an attribute to specify textual copy, this makes it hard to add further HTML elements to the value of `@title` which would not be supported in the HTML `<title>` element.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{{page-title @title separator=(or @separator ' - ')}}
|
||||
<PortalTarget @name="route-announcer" />
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
{{did-insert this.connect}}
|
||||
{{will-destroy this.disconnect}}
|
||||
|
||||
{{#if this.title}}
|
||||
{{page-title this.title separator=@titleSeparator}}
|
||||
{{/if}}
|
||||
|
||||
{{yield (hash
|
||||
model=this.model
|
||||
params=this.params
|
||||
currentName=this.router.currentRoute.name
|
||||
|
||||
refresh=this.refresh
|
||||
|
||||
Title=(component "route/title")
|
||||
Announcer=(component "route/announcer")
|
||||
)}}
|
|
@ -5,13 +5,10 @@ import { tracked } from '@glimmer/tracking';
|
|||
|
||||
export default class RouteComponent extends Component {
|
||||
@service('routlet') routlet;
|
||||
@service('router') router;
|
||||
|
||||
@tracked model;
|
||||
|
||||
get title() {
|
||||
return this.args.title;
|
||||
}
|
||||
|
||||
get params() {
|
||||
return this.routlet.paramsFor(this.args.name);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{{page-title @title separator=@separator}}
|
||||
{{#if (not-eq @render false)}}
|
||||
{{@title}}
|
||||
{{/if}}
|
||||
<Portal @target="route-announcer">
|
||||
<div
|
||||
class="route-title"
|
||||
...attributes
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
>
|
||||
{{! Using a handlebars concat here avoid whitespace issues}}
|
||||
{{concat 'Navigated to ' @title}}
|
||||
</div>
|
||||
</Portal>
|
|
@ -0,0 +1,6 @@
|
|||
%route-title {
|
||||
@extend %visually-hidden;
|
||||
}
|
||||
.route-title {
|
||||
@extend %route-title;
|
||||
}
|
|
@ -7,6 +7,7 @@ class: ember
|
|||
<TokenSource
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
@partition={{partition}}
|
||||
@type={{or 'oidc' 'secret'}}
|
||||
@value={{or identifierForProvider secret}}
|
||||
@onchange={{action 'change'}}
|
||||
|
@ -20,6 +21,7 @@ class: ember
|
|||
| --- | --- | --- | --- |
|
||||
| `dc` | `String` | | The name of the current datacenter |
|
||||
| `nspace` | `String` | | The name of the current namespace |
|
||||
| `partition` | `String` | | The name of the current partition |
|
||||
| `onchange` | `Function` | | The action to fire when the data changes. Emits an Event-like object with a `data` property containing the jwt data, in this case the autorizationCode and the status |
|
||||
| `onerror` | `Function` | | The action to fire when an error occurs. Emits ErrorEvent object with an `error` property containing the Error. |
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<StateChart @src={{chart}} @initial={{if (eq type 'oidc') 'provider' 'secret'}} as |State Guard Action dispatch state|>
|
||||
<Guard @name="isSecret" @cond={{action 'isSecret'}} />
|
||||
{{!-- This `or` can be completely removed post 1.10 as 1.10 has optional params with default values --}}
|
||||
{{#let (concat '/' (or nspace '') '/' dc) as |path|}}
|
||||
{{#let (concat '/' (or partition '') '/' (or nspace '') '/' dc) as |path|}}
|
||||
<State @matches="secret">
|
||||
<DataSource
|
||||
@src={{concat path '/token/self/' value}}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<TopologyMetrics::Stats
|
||||
data-test-topology-metrics-downstream-stats
|
||||
@nspace={{or item.Namespace 'default'}}
|
||||
@partition={{or item.Partition 'default'}}
|
||||
@dc={{item.Datacenter}}
|
||||
@endpoint='downstream-summary-for-service'
|
||||
@service={{@service.Service.Service}}
|
||||
|
@ -48,6 +49,7 @@
|
|||
{{#if @hasMetricsProvider}}
|
||||
<TopologyMetrics::Series
|
||||
@nspace={{or @service.Service.Namespace 'default'}}
|
||||
@partition={{or service.Service.Partition 'default'}}
|
||||
@dc={{@dc}}
|
||||
@service={{@service.Service.Service}}
|
||||
@protocol={{@topology.Protocol}}
|
||||
|
@ -56,6 +58,7 @@
|
|||
{{#if this.mainNotIngressService}}
|
||||
<TopologyMetrics::Stats
|
||||
@nspace={{or @service.Service.Namespace 'default'}}
|
||||
@partition={{or service.Service.Partition 'default'}}
|
||||
@dc={{@dc}}
|
||||
@endpoint='summary-for-service'
|
||||
@service={{@service.Service.Service}}
|
||||
|
@ -104,6 +107,7 @@
|
|||
{{!-- One of the only places in the app where it's acceptable to default to 'default' namespace --}}
|
||||
<TopologyMetrics::Stats
|
||||
@nspace={{or item.Namespace 'default'}}
|
||||
@partition={{or item.Partition 'default'}}
|
||||
@dc={{item.Datacenter}}
|
||||
@endpoint='upstream-summary-for-service'
|
||||
@service={{@service.Service.Service}}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{{#if (not @noMetricsReason)}}
|
||||
<DataSource
|
||||
@src={{uri
|
||||
'/${nspace}/${dc}/metrics/summary-for-service/${service}/${protocol}'
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/metrics/summary-for-service/${service}/${protocol}'
|
||||
(hash
|
||||
nspace=@nspace
|
||||
partition=@partition
|
||||
dc=@dc
|
||||
service=@service
|
||||
protocol=@protocol
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{{#if (not @noMetricsReason)}}
|
||||
<DataSource
|
||||
@src={{uri
|
||||
'/${nspace}/${dc}/metrics/${endpoint}/${service}/${protocol}'
|
||||
'/${partition}/${nspace}/${dc}/metrics/${endpoint}/${service}/${protocol}'
|
||||
(hash
|
||||
nspace=@nspace
|
||||
partition=@partition
|
||||
dc=@dc
|
||||
endpoint=@endpoint
|
||||
service=@service
|
||||
|
|
|
@ -5,14 +5,9 @@ import { get, action } from '@ember/object';
|
|||
import transitionable from 'consul-ui/utils/routing/transitionable';
|
||||
|
||||
export default class ApplicationController extends Controller {
|
||||
@service('router')
|
||||
router;
|
||||
|
||||
@service('store')
|
||||
store;
|
||||
|
||||
@service('feedback')
|
||||
feedback;
|
||||
@service('router') router;
|
||||
@service('store') store;
|
||||
@service('feedback') feedback;
|
||||
|
||||
// TODO: We currently do this in the controller instead of the router
|
||||
// as the nspace and dc variables aren't available directly on the Route
|
||||
|
@ -29,7 +24,7 @@ export default class ApplicationController extends Controller {
|
|||
// TODO: Currently we clear cache from the ember-data store
|
||||
// ideally this would be a static method of the abstract Repository class
|
||||
// once we move to proper classes for services take another look at this.
|
||||
this.store.clear();
|
||||
this.store.invalidate();
|
||||
//
|
||||
const params = {};
|
||||
if (e.data) {
|
||||
|
|
|
@ -7,25 +7,11 @@ export default Controller.extend({
|
|||
this._super(...arguments);
|
||||
this.form = this.builder.form('nspace');
|
||||
},
|
||||
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);
|
||||
const form = this.form;
|
||||
try {
|
||||
form.handleEvent(event);
|
||||
this.form.handleEvent(event);
|
||||
} catch (err) {
|
||||
const target = event.target;
|
||||
switch (target.name) {
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
dom: service('dom'),
|
||||
actions: {
|
||||
change: function(e, value, item) {
|
||||
const event = this.dom.normalizeEvent(e, value);
|
||||
// TODO: Switch to using forms like the rest of the app
|
||||
// setting utils/form/builder for things to be done before we
|
||||
// can do that. For the moment just do things normally its a simple
|
||||
// enough form at the moment
|
||||
|
||||
const target = event.target;
|
||||
const blocking = get(this, 'item.client.blocking');
|
||||
switch (target.name) {
|
||||
case 'client[blocking]':
|
||||
set(this, 'item.client.blocking', !blocking);
|
||||
this.send('update', 'client', this.item.client);
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default {
|
||||
name: 'routing',
|
||||
initialize(application) {
|
||||
application.register('route:basic', Route);
|
||||
},
|
||||
};
|
|
@ -13,7 +13,7 @@ export default {
|
|||
let repositories = container
|
||||
.get('container-debug-adapter:main')
|
||||
.catalogEntriesByType('service')
|
||||
.filter(item => item.startsWith('repository/'));
|
||||
.filter(item => item.startsWith('repository/') || item === 'ui-config');
|
||||
|
||||
// during testing we get -test files in here, filter those out but only in debug envs
|
||||
runInDebug(() => (repositories = repositories.filter(item => !item.endsWith('-test'))));
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
export function initialize(container) {
|
||||
const env = container.lookup('service:env');
|
||||
if (env.var('CONSUL_NSPACES_ENABLED')) {
|
||||
// enable the nspace repo
|
||||
['dc', 'settings', 'dc.intentions.edit', 'dc.intentions.create'].forEach(function(item) {
|
||||
container.inject(`route:${item}`, 'nspacesRepo', 'service:repository/nspace/enabled');
|
||||
container.inject(`route:nspace.${item}`, 'nspacesRepo', 'service:repository/nspace/enabled');
|
||||
});
|
||||
container.inject('route:application', 'nspacesRepo', 'service:repository/nspace/enabled');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
|
@ -165,7 +165,7 @@ export default class FSMWithOptionalLocation {
|
|||
|
||||
optionalParams() {
|
||||
let optional = this.optional || {};
|
||||
return Object.keys(OPTIONAL).reduce((prev, item) => {
|
||||
return ['partition', 'nspace'].reduce((prev, item) => {
|
||||
let value = '';
|
||||
if (typeof optional[item] !== 'undefined') {
|
||||
value = optional[item].match;
|
||||
|
@ -263,7 +263,7 @@ export default class FSMWithOptionalLocation {
|
|||
optional = undefined;
|
||||
}
|
||||
optional = Object.values(optional || this.optional || {});
|
||||
optional = optional.map(item => item.value || item, []);
|
||||
optional = optional.filter(item => Boolean(item)).map(item => item.value || item, []);
|
||||
temp.splice(...[1, 0].concat(optional));
|
||||
url = temp.join('/');
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
/**
|
||||
* Used for create-type Routes
|
||||
*
|
||||
* 'repo' is standardized across the app
|
||||
* 'item' is standardized across the app
|
||||
* they could be replaced with `getRepo` and `getItem`
|
||||
*/
|
||||
export default Mixin.create({
|
||||
beforeModel: function() {
|
||||
this._super(...arguments);
|
||||
this.repo.invalidate();
|
||||
},
|
||||
deactivate: function() {
|
||||
this._super(...arguments);
|
||||
// TODO: This is dependent on ember-changeset
|
||||
// Change changeset to support ember-data props
|
||||
const item = get(this.controller, 'item.data');
|
||||
// TODO: Look and see if rollbackAttributes is good here
|
||||
if (get(item, 'isNew')) {
|
||||
item.destroyRecord();
|
||||
}
|
||||
},
|
||||
});
|
|
@ -1,4 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
|
@ -1,4 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
|
@ -1,4 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
|
@ -1,48 +0,0 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {
|
||||
settings: service('settings'),
|
||||
actions: {
|
||||
use: function(item) {
|
||||
return this.repo
|
||||
.findBySlug({
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
ns: get(item, 'Namespace'),
|
||||
id: get(item, 'AccessorID'),
|
||||
})
|
||||
.then(item => {
|
||||
return this.settings.persist({
|
||||
token: {
|
||||
AccessorID: get(item, 'AccessorID'),
|
||||
SecretID: get(item, 'SecretID'),
|
||||
Namespace: get(item, 'Namespace'),
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
logout: function(item) {
|
||||
return this.settings.delete('token');
|
||||
},
|
||||
clone: function(item) {
|
||||
let cloned;
|
||||
return this.feedback.execute(() => {
|
||||
return this.repo
|
||||
.clone(item)
|
||||
.then(item => {
|
||||
cloned = item;
|
||||
// cloning is similar to delete in that
|
||||
// if you clone from the listing page, stay on the listing page
|
||||
// whereas if you clone from another token, take me back to the listing page
|
||||
// so I can see it
|
||||
return this.afterDelete(...arguments);
|
||||
})
|
||||
.then(function() {
|
||||
return cloned;
|
||||
});
|
||||
}, 'clone');
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set } from '@ember/object';
|
||||
import { set, get } from '@ember/object';
|
||||
/** With Blocking Actions
|
||||
* This mixin contains common write actions (Create Update Delete) for routes.
|
||||
* It could also be an Route to extend but decoration seems to be more sense right now.
|
||||
|
@ -18,6 +18,7 @@ import { set } from '@ember/object';
|
|||
*/
|
||||
export default Mixin.create({
|
||||
_feedback: service('feedback'),
|
||||
settings: service('settings'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
const feedback = this._feedback;
|
||||
|
@ -107,5 +108,45 @@ export default Mixin.create({
|
|||
}
|
||||
);
|
||||
},
|
||||
use: function(item) {
|
||||
return this.repo
|
||||
.findBySlug({
|
||||
dc: get(item, 'Datacenter'),
|
||||
ns: get(item, 'Namespace'),
|
||||
partition: get(item, 'Partition'),
|
||||
id: get(item, 'AccessorID'),
|
||||
})
|
||||
.then(item => {
|
||||
return this.settings.persist({
|
||||
token: {
|
||||
AccessorID: get(item, 'AccessorID'),
|
||||
SecretID: get(item, 'SecretID'),
|
||||
Namespace: get(item, 'Namespace'),
|
||||
Partition: get(item, 'Partition'),
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
logout: function(item) {
|
||||
return this.settings.delete('token');
|
||||
},
|
||||
clone: function(item) {
|
||||
let cloned;
|
||||
return this.feedback.execute(() => {
|
||||
return this.repo
|
||||
.clone(item)
|
||||
.then(item => {
|
||||
cloned = item;
|
||||
// cloning is similar to delete in that
|
||||
// if you clone from the listing page, stay on the listing page
|
||||
// whereas if you clone from another token, take me back to the listing page
|
||||
// so I can see it
|
||||
return this.afterDelete(...arguments);
|
||||
})
|
||||
.then(function() {
|
||||
return cloned;
|
||||
});
|
||||
}, 'clone');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ export default class AuthMethod extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string', { defaultValue: () => '' }) Description;
|
||||
@attr('string', { defaultValue: () => '' }) DisplayName;
|
||||
@attr('string', { defaultValue: () => 'local' }) TokenLocality;
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class BindingRule extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string', { defaultValue: () => '' }) Description;
|
||||
@attr('string') AuthMethod;
|
||||
@attr('string', { defaultValue: () => '' }) Selector;
|
||||
|
|
|
@ -10,5 +10,6 @@ export default class Coordinate extends Model {
|
|||
@attr() Coord; // {Vec, Error, Adjustment, Height}
|
||||
@attr('string') Segment;
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Partition;
|
||||
@attr('number') SyncTime;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ export default class DiscoveryChain extends Model {
|
|||
@attr('string') ServiceName;
|
||||
|
||||
@attr('string') Datacenter;
|
||||
// FIXME: Does this need partition?
|
||||
@attr() Chain; // {}
|
||||
@attr() meta; // {}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ export default class Intention extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Description;
|
||||
// FIXME: Will we have Source/DestinationPartition?
|
||||
@attr('string', { defaultValue: () => 'default' }) SourceNS;
|
||||
@attr('string', { defaultValue: () => '*' }) SourceName;
|
||||
@attr('string', { defaultValue: () => 'default' }) DestinationNS;
|
||||
|
|
|
@ -14,6 +14,7 @@ export default class Kv extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('number') LockIndex;
|
||||
@attr('number') Flags;
|
||||
@nullValue(undefined) @attr('string') Value;
|
||||
|
|
|
@ -11,6 +11,7 @@ export default class Node extends Model {
|
|||
@attr('string') ID;
|
||||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Partition;
|
||||
@attr('string') Address;
|
||||
@attr('string') Node;
|
||||
@attr('number') SyncTime;
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import Model, { attr } from '@ember-data/model';
|
||||
|
||||
export const PRIMARY_KEY = 'Name';
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'Name';
|
||||
export const NSPACE_KEY = 'Namespace';
|
||||
|
||||
export default class Nspace extends Model {
|
||||
@attr('string') uid;
|
||||
@attr('string') Name;
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Partition;
|
||||
// Namespace is the same as Name but please don't alias as we want to keep
|
||||
// mutating the response here instead
|
||||
@attr('string') Namespace;
|
||||
|
||||
@attr('number') SyncTime;
|
||||
@attr('string', { defaultValue: () => '' }) Description;
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class OidcProvider extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string') Kind;
|
||||
@attr('string') AuthURL;
|
||||
@attr('string') DisplayName;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'Name';
|
||||
export const PARTITION_KEY = 'Partition';
|
||||
|
||||
export default class PartitionModel extends Model {
|
||||
@attr('string') uid;
|
||||
@attr('string') Name;
|
||||
@attr('string') Description;
|
||||
@attr('string') Datacenter;
|
||||
|
||||
@attr('string') Namespace; // always ""
|
||||
// Partition is the same as Name but please don't alias as we want to keep
|
||||
// mutating the response here instead
|
||||
@attr('string') Partition;
|
||||
|
||||
@attr('number') SyncTime;
|
||||
@attr() meta;
|
||||
}
|
|
@ -11,6 +11,7 @@ export default class Policy extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string', { defaultValue: () => '' }) Name;
|
||||
@attr('string', { defaultValue: () => '' }) Description;
|
||||
@attr('string', { defaultValue: () => '' }) Rules;
|
||||
|
|
|
@ -11,6 +11,7 @@ export default class Proxy extends ServiceInstanceModel {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
// FIXME: Does this need a partition?
|
||||
@attr('string') ServiceName;
|
||||
@attr('string') ServiceID;
|
||||
@attr('string') NodeName;
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class Role extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string', { defaultValue: () => '' }) Name;
|
||||
@attr('string', { defaultValue: () => '' }) Description;
|
||||
@attr({ defaultValue: () => [] }) Policies;
|
||||
|
|
|
@ -51,6 +51,9 @@ export default class ServiceInstance extends Model {
|
|||
@alias('Service.Meta') Meta;
|
||||
@alias('Service.Namespace') Namespace;
|
||||
|
||||
// FIXME: Is parition top level or Service.Partition?
|
||||
@attr('string') Partition;
|
||||
|
||||
@filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'service') ServiceChecks;
|
||||
@filter('Checks.@each.Kind', (item, i, arr) => item.Kind === 'node') NodeChecks;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ export default class Service extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string') Kind;
|
||||
@attr('number') ChecksPassing;
|
||||
@attr('number') ChecksCritical;
|
||||
|
|
|
@ -12,6 +12,7 @@ export default class Session extends Model {
|
|||
@attr('string') Name;
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string') Node;
|
||||
@attr('string') Behavior;
|
||||
@attr('string') TTL;
|
||||
|
|
|
@ -11,6 +11,7 @@ export default class Token extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string') IDPName;
|
||||
@attr('string') SecretID;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export default class Topology extends Model {
|
|||
|
||||
@attr('string') Datacenter;
|
||||
@attr('string') Namespace;
|
||||
@attr('string') Partition;
|
||||
@attr('string') Protocol;
|
||||
@attr('boolean') FilteredByACLs;
|
||||
@attr('boolean') TransparentProxy;
|
||||
|
|
|
@ -133,7 +133,7 @@ export const routes = {
|
|||
abilities: ['access acls'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
_options: { path: '/:acl' },
|
||||
},
|
||||
create: {
|
||||
_options: {
|
||||
|
@ -174,7 +174,7 @@ export const routes = {
|
|||
tokens: {
|
||||
_options: {
|
||||
path: '/tokens',
|
||||
abilities: ['read tokens'],
|
||||
abilities: env('CONSUL_ACLS_ENABLED') ? ['read tokens'] : ['access acls'],
|
||||
},
|
||||
edit: {
|
||||
_options: { path: '/:id' },
|
||||
|
@ -219,7 +219,7 @@ export const routes = {
|
|||
_options: { path: '/setting' },
|
||||
},
|
||||
notfound: {
|
||||
_options: { path: '/*path' },
|
||||
_options: { path: '/*notfound' },
|
||||
},
|
||||
};
|
||||
if (env('CONSUL_NSPACES_ENABLED')) {
|
||||
|
|
|
@ -1,93 +1,24 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Route.extend(WithBlockingActions, {
|
||||
router: service('router'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
repo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
model: function() {
|
||||
return hash({
|
||||
router: this.router,
|
||||
dcs: this.repo.findAll(),
|
||||
nspaces: this.nspacesRepo.findAll().catch(function() {
|
||||
return [];
|
||||
}),
|
||||
|
||||
// these properties are added to the controller from route/dc
|
||||
// as we don't have access to the dc and nspace params in the URL
|
||||
// until we get to the route/dc route
|
||||
// permissions also requires the dc param
|
||||
|
||||
// dc: null,
|
||||
// nspace: null
|
||||
// token: null
|
||||
// permissions: null
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
this._super(...arguments);
|
||||
controller.setProperties(model);
|
||||
},
|
||||
actions: {
|
||||
error: function(e, transition) {
|
||||
// TODO: Normalize all this better
|
||||
let error = {
|
||||
status: e.code || e.statusCode || '',
|
||||
message: e.message || e.detail || 'Error',
|
||||
};
|
||||
if (e.errors && e.errors[0]) {
|
||||
error = e.errors[0];
|
||||
error.message = error.message || error.title || error.detail || 'Error';
|
||||
}
|
||||
if (error.status === '') {
|
||||
error.message = 'Error';
|
||||
}
|
||||
// Try and get the currently attempted dc, whereever that may be
|
||||
let model = this.modelFor('dc') || this.modelFor('nspace.dc');
|
||||
if (!model) {
|
||||
const path = new URL(location.href).pathname
|
||||
.substr(this.router.rootURL.length - 1)
|
||||
.split('/')
|
||||
.slice(1, 3);
|
||||
model = {
|
||||
nspace: { Name: 'default' },
|
||||
};
|
||||
if (path[0].startsWith('~')) {
|
||||
model.nspace = {
|
||||
Name: path.shift(),
|
||||
};
|
||||
}
|
||||
model.dc = {
|
||||
Name: path[0],
|
||||
};
|
||||
}
|
||||
const app = this.modelFor('application') || {};
|
||||
const dcs = app.dcs || [model.dc];
|
||||
const nspaces = app.nspaces || [model.nspace];
|
||||
hash({
|
||||
dc:
|
||||
error.status.toString().indexOf('5') !== 0
|
||||
? this.repo.getActive(model.dc.Name, dcs)
|
||||
: { Name: 'Error' },
|
||||
dcs: dcs,
|
||||
nspace: model.nspace,
|
||||
nspaces: nspaces,
|
||||
})
|
||||
.then(model => Promise.all([model, this.repo.clearActive()]))
|
||||
.then(([model]) => {
|
||||
// we can't use setupController as we received an error
|
||||
// so we do it manually instead
|
||||
this.controllerFor('application').setProperties(model);
|
||||
this.controllerFor('error').setProperties({ error: error });
|
||||
})
|
||||
.catch(e => {
|
||||
this.controllerFor('error').setProperties({ error: error });
|
||||
});
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
export default class ApplicationRoute extends Route.extend(WithBlockingActions) {
|
||||
@action
|
||||
error(e, transition) {
|
||||
// TODO: Normalize all this better
|
||||
let error = {
|
||||
status: e.code || e.statusCode || '',
|
||||
message: e.message || e.detail || 'Error',
|
||||
};
|
||||
if (e.errors && e.errors[0]) {
|
||||
error = e.errors[0];
|
||||
error.message = error.message || error.title || error.detail || 'Error';
|
||||
}
|
||||
if (error.status === '') {
|
||||
error.message = 'Error';
|
||||
}
|
||||
this.controllerFor('application').setProperties({ error: error });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { get, action } from '@ember/object';
|
||||
|
||||
// TODO: We should potentially move all these nspace related things
|
||||
// up a level to application.js
|
||||
|
||||
export default class DcRoute extends Route {
|
||||
@service('repository/dc') repo;
|
||||
@service('repository/permission') permissionsRepo;
|
||||
@service('repository/nspace/disabled') nspacesRepo;
|
||||
@service('settings') settingsRepo;
|
||||
|
||||
async model(params) {
|
||||
let [token, nspace, dc] = await Promise.all([
|
||||
this.settingsRepo.findBySlug('token'),
|
||||
this.nspacesRepo.getActive(this.optionalParams().nspace),
|
||||
this.repo.findBySlug(params.dc),
|
||||
]);
|
||||
|
||||
// When disabled nspaces is [], so nspace is undefined
|
||||
const permissions = await this.permissionsRepo.findAll({
|
||||
dc: params.dc,
|
||||
ns: get(nspace || {}, 'Name'),
|
||||
ns: this.optionalParams().nspace,
|
||||
});
|
||||
// the model here is actually required for the entire application
|
||||
// but we need to wait until we are in this route so we know what the dc
|
||||
|
@ -30,49 +20,10 @@ export default class DcRoute extends Route {
|
|||
// We do this here instead of in setupController to prevent timing issues
|
||||
// in lower routes
|
||||
this.controllerFor('application').setProperties({
|
||||
dc,
|
||||
nspace,
|
||||
token,
|
||||
permissions,
|
||||
});
|
||||
return {
|
||||
dc,
|
||||
nspace,
|
||||
token,
|
||||
permissions,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: This will eventually be deprecated please see
|
||||
// https://deprecations.emberjs.com/v3.x/#toc_deprecate-router-events
|
||||
@action
|
||||
willTransition(transition) {
|
||||
if (
|
||||
typeof transition !== 'undefined' &&
|
||||
(transition.from.name.endsWith('nspaces.create') ||
|
||||
transition.from.name.startsWith('nspace.dc.acls.tokens'))
|
||||
) {
|
||||
// Only when we create, reload the nspaces in the main menu to update them
|
||||
// as we don't block for those
|
||||
// And also when we [Use] a token reload the nspaces that you are able to see,
|
||||
// including your permissions for being able to manage namespaces
|
||||
// Potentially we should just do this on every single transition
|
||||
// but then we would need to check to see if nspaces are enabled
|
||||
const controller = this.controllerFor('application');
|
||||
Promise.all([
|
||||
this.nspacesRepo.findAll(),
|
||||
this.permissionsRepo.findAll({
|
||||
dc: get(controller, 'dc.Name'),
|
||||
nspace: get(controller, 'nspace.Name'),
|
||||
}),
|
||||
]).then(([nspaces, permissions]) => {
|
||||
if (typeof controller !== 'undefined') {
|
||||
controller.setProperties({
|
||||
nspaces: nspaces,
|
||||
permissions: permissions,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class IndexRoute extends Route {
|
||||
@service('repository/auth-method') repo;
|
||||
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
source: 'source',
|
||||
|
@ -18,21 +14,4 @@ export default class IndexRoute extends Route {
|
|||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
model(params) {
|
||||
return hash({
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter({
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
ns: this.optionalParams().nspace,
|
||||
}),
|
||||
}),
|
||||
searchProperties: this.queryParams.searchproperty.empty[0],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class ShowRoute extends SingleRoute {
|
||||
@service('repository/auth-method') repo;
|
||||
@service('repository/binding-rule') bindingRuleRepo;
|
||||
|
||||
model(params) {
|
||||
const dc = this.modelFor('dc').dc;
|
||||
const nspace = this.optionalParams().nspace;
|
||||
|
||||
return super.model(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
item: this.repo.findBySlug({
|
||||
id: params.id,
|
||||
dc: dc.Name,
|
||||
ns: nspace,
|
||||
}),
|
||||
bindingRules: this.bindingRuleRepo.findAllByDatacenter({
|
||||
ns: nspace,
|
||||
dc: dc.Name,
|
||||
authmethod: params.id,
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class AuthMethodRoute extends Route {
|
||||
model(params) {
|
||||
const parent = this.routeName
|
||||
.split('.')
|
||||
.slice(0, -1)
|
||||
.join('.');
|
||||
return this.modelFor(parent);
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class BindingRulesRoute extends Route {
|
||||
model() {
|
||||
const parent = this.routeName
|
||||
.split('.')
|
||||
.slice(0, -1)
|
||||
.join('.');
|
||||
return this.modelFor(parent);
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
import to from 'consul-ui/utils/routing/redirect-to';
|
||||
|
||||
export default Route.extend({
|
||||
redirect: to('auth-method'),
|
||||
});
|
||||
export default class AuthMethodShowIndexRoute extends Route {
|
||||
redirect = to('auth-method');
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class NspaceRulesRoute extends Route {
|
||||
model() {
|
||||
const parent = this.routeName
|
||||
.split('.')
|
||||
.slice(0, -1)
|
||||
.join('.');
|
||||
return this.modelFor(parent);
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default class CreateRoute extends Route.extend(CreatingRoute) {
|
||||
export default class CreateRoute extends Route {
|
||||
templateName = 'dc/acls/policies/edit';
|
||||
}
|
||||
|
|
|
@ -1,48 +1,8 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class EditRoute extends SingleRoute.extend(WithPolicyActions) {
|
||||
@service('repository/policy')
|
||||
repo;
|
||||
|
||||
@service('repository/token')
|
||||
tokenRepo;
|
||||
|
||||
model(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.optionalParams().nspace;
|
||||
const tokenRepo = this.tokenRepo;
|
||||
return super.model(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
routeName: this.routeName,
|
||||
items: tokenRepo
|
||||
.findByPolicy({
|
||||
ns: nspace,
|
||||
dc: dc,
|
||||
id: get(model.item, 'ID'),
|
||||
})
|
||||
.catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
export default class EditRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/policy') repo;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import WithPolicyActions from 'consul-ui/mixins/policy/with-actions';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class IndexRoute extends Route.extend(WithPolicyActions) {
|
||||
export default class IndexRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/policy') repo;
|
||||
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
datacenter: {
|
||||
|
@ -22,21 +20,4 @@ export default class IndexRoute extends Route.extend(WithPolicyActions) {
|
|||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
model(params) {
|
||||
return hash({
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter({
|
||||
ns: this.optionalParams().nspace,
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
}),
|
||||
}),
|
||||
searchProperties: this.queryParams.searchproperty.empty[0],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default class CreateRoute extends Route.extend(CreatingRoute) {
|
||||
export default class CreateRoute extends Route {
|
||||
templateName = 'dc/acls/roles/edit';
|
||||
}
|
||||
|
|
|
@ -1,47 +1,8 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class EditRoute extends SingleRoute.extend(WithRoleActions) {
|
||||
@service('repository/role')
|
||||
repo;
|
||||
|
||||
@service('repository/token')
|
||||
tokenRepo;
|
||||
|
||||
model(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.optionalParams().nspace;
|
||||
const tokenRepo = this.tokenRepo;
|
||||
return super.model(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: tokenRepo
|
||||
.findByRole({
|
||||
ns: nspace,
|
||||
dc: dc,
|
||||
id: get(model.item, 'ID'),
|
||||
})
|
||||
.catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
// do nothing the SingleRoute will have caught it already
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
export default class EditRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/role') repo;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import WithRoleActions from 'consul-ui/mixins/role/with-actions';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class IndexRoute extends Route.extend(WithRoleActions) {
|
||||
export default class IndexRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/role') repo;
|
||||
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
searchproperty: {
|
||||
|
@ -18,21 +16,4 @@ export default class IndexRoute extends Route.extend(WithRoleActions) {
|
|||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
model(params) {
|
||||
return hash({
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter({
|
||||
ns: this.optionalParams().nspace,
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
}),
|
||||
}),
|
||||
searchProperties: this.queryParams.searchproperty.empty[0],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default class CreateRoute extends Route.extend(CreatingRoute) {
|
||||
export default class CreateRoute extends Route {
|
||||
templateName = 'dc/acls/tokens/edit';
|
||||
}
|
||||
|
|
|
@ -1,30 +1,15 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import SingleRoute from 'consul-ui/routing/single';
|
||||
import { hash } from 'rsvp';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class EditRoute extends SingleRoute.extend(WithTokenActions) {
|
||||
@service('repository/token')
|
||||
repo;
|
||||
export default class EditRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/token') repo;
|
||||
@service('settings') settings;
|
||||
|
||||
@service('settings')
|
||||
settings;
|
||||
|
||||
model(params, transition) {
|
||||
return super.model(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
routeName: this.routeName,
|
||||
token: this.settings.findBySlug('token'),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
async model(params, transition) {
|
||||
return {
|
||||
token: await this.settings.findBySlug('token'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
import WithTokenActions from 'consul-ui/mixins/token/with-actions';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class IndexRoute extends Route.extend(WithTokenActions) {
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default class IndexRoute extends Route.extend(WithBlockingActions) {
|
||||
@service('repository/token') repo;
|
||||
@service('settings') settings;
|
||||
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
kind: 'kind',
|
||||
|
@ -20,35 +17,4 @@ export default class IndexRoute extends Route.extend(WithTokenActions) {
|
|||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
async beforeModel(transition) {
|
||||
const token = await this.settings.findBySlug('token');
|
||||
// If you have a token set with AccessorID set to null (legacy mode)
|
||||
// then rewrite to the old 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');
|
||||
}
|
||||
}
|
||||
|
||||
model(params) {
|
||||
const nspace = this.optionalParams().nspace;
|
||||
return hash({
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter({
|
||||
ns: nspace,
|
||||
dc: this.modelFor('dc').dc.Name,
|
||||
}),
|
||||
}),
|
||||
nspace: nspace,
|
||||
token: this.settings.findBySlug('token'),
|
||||
searchProperties: this.queryParams.searchproperty.empty[0],
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
import to from 'consul-ui/utils/routing/redirect-to';
|
||||
|
||||
export default class IndexRoute extends Route {
|
||||
beforeModel() {
|
||||
this.transitionTo('dc.services');
|
||||
}
|
||||
redirect = to('services');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Route from './edit';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class CreateRoute extends Route {
|
||||
templateName = 'dc/intentions/edit';
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class EditRoute extends Route {
|
||||
@service('repository/intention') repo;
|
||||
@service('env') env;
|
||||
|
||||
async model(params, transition) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.optionalParams().nspace;
|
||||
|
||||
let item;
|
||||
if (typeof params.intention_id !== 'undefined') {
|
||||
item = await this.repo.findBySlug({
|
||||
ns: nspace,
|
||||
dc: dc,
|
||||
id: params.intention_id,
|
||||
});
|
||||
} else {
|
||||
const defaultNspace = this.env.var('CONSUL_NSPACES_ENABLED') ? '*' : 'default';
|
||||
item = await this.repo.create({
|
||||
SourceNS: nspace || defaultNspace,
|
||||
DestinationNS: nspace || defaultNspace,
|
||||
Datacenter: dc,
|
||||
});
|
||||
}
|
||||
return {
|
||||
dc,
|
||||
nspace,
|
||||
item,
|
||||
};
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
super.setupController(...arguments);
|
||||
controller.setProperties(model);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue