mirror of
https://github.com/status-im/consul.git
synced 2025-01-22 03:29:43 +00:00
ui: Namespace Support (#6639)
Adds namespace support to the UI: 1. Namespace CRUD/management 2. Show Namespace in relevant areas (intentions, upstreams) 3. Main navigation bar improvements 4. Logic/integration to interact with a new `internal/acl/authorize` endpoint
This commit is contained in:
parent
f06c3adca5
commit
7044aa52c8
@ -2,6 +2,8 @@ import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './applica
|
||||
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 Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index }) {
|
||||
// https://www.consul.io/api/acl.html#list-acls
|
||||
|
@ -1,10 +1,22 @@
|
||||
import Adapter from './http';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'consul-ui/config/environment';
|
||||
|
||||
export const DATACENTER_QUERY_PARAM = 'dc';
|
||||
export const NSPACE_QUERY_PARAM = 'ns';
|
||||
export default Adapter.extend({
|
||||
repo: service('settings'),
|
||||
client: service('client/http'),
|
||||
formatNspace: function(nspace) {
|
||||
if (config.CONSUL_NSPACES_ENABLED) {
|
||||
return nspace !== '' ? { [NSPACE_QUERY_PARAM]: nspace } : undefined;
|
||||
}
|
||||
},
|
||||
formatDatacenter: function(dc) {
|
||||
return {
|
||||
[DATACENTER_QUERY_PARAM]: dc,
|
||||
};
|
||||
},
|
||||
// TODO: kinda protected for the moment
|
||||
// decide where this should go either read/write from http
|
||||
// should somehow use this or vice versa
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Adapter from './application';
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index }) {
|
||||
return request`
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Adapter from 'ember-data/adapter';
|
||||
import AdapterError from '@ember-data/adapter/error';
|
||||
import {
|
||||
AbortError,
|
||||
TimeoutError,
|
||||
@ -8,9 +9,7 @@ import {
|
||||
NotFoundError,
|
||||
ConflictError,
|
||||
InvalidError,
|
||||
AdapterError,
|
||||
} from 'ember-data/adapters/errors';
|
||||
|
||||
// TODO: This is a little skeleton cb function
|
||||
// is to be replaced soon with something slightly more involved
|
||||
const responder = function(response) {
|
||||
|
@ -1,6 +1,10 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { SLUG_KEY } from 'consul-ui/models/intention';
|
||||
// Intentions use SourceNS and DestinationNS properties for namespacing
|
||||
// so we don't need to add the `?ns=` anywhere here
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
return request`
|
||||
@ -24,14 +28,30 @@ export default Adapter.extend({
|
||||
return request`
|
||||
POST /v1/connect/intentions?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
|
||||
${serialized}
|
||||
${{
|
||||
SourceNS: serialized.SourceNS,
|
||||
DestinationNS: serialized.DestinationNS,
|
||||
SourceName: serialized.SourceName,
|
||||
DestinationName: serialized.DestinationName,
|
||||
SourceType: serialized.SourceType,
|
||||
Action: serialized.Action,
|
||||
Description: serialized.Description,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
return request`
|
||||
PUT /v1/connect/intentions/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
|
||||
${serialized}
|
||||
${{
|
||||
SourceNS: serialized.SourceNS,
|
||||
DestinationNS: serialized.DestinationNS,
|
||||
SourceName: serialized.SourceName,
|
||||
DestinationName: serialized.DestinationName,
|
||||
SourceType: serialized.SourceType,
|
||||
Action: serialized.Action,
|
||||
Description: serialized.Description,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
|
@ -1,46 +1,62 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import Adapter from './application';
|
||||
|
||||
import isFolder from 'consul-ui/utils/isFolder';
|
||||
import keyToArray from 'consul-ui/utils/keyToArray';
|
||||
|
||||
import { SLUG_KEY } from 'consul-ui/models/kv';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
const API_KEYS_KEY = 'keys';
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id, separator }) {
|
||||
requestForQuery: function(request, { dc, ns, index, id, separator }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/kv/${keyToArray(id)}?${{ [API_KEYS_KEY]: null, dc, separator }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/kv/${keyToArray(id)}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
// TODO: Should we replace text/plain here with x-www-form-encoded?
|
||||
// See https://github.com/hashicorp/consul/issues/3804
|
||||
requestForCreateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${params}
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
${serialized}
|
||||
`;
|
||||
},
|
||||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/kv/${keyToArray(data[SLUG_KEY])}?${params}
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
${serialized}
|
||||
@ -51,11 +67,13 @@ export default Adapter.extend({
|
||||
if (isFolder(data[SLUG_KEY])) {
|
||||
recurse = null;
|
||||
}
|
||||
return request`
|
||||
DELETE /v1/kv/${keyToArray(data[SLUG_KEY])}?${{
|
||||
[API_DATACENTER_KEY]: data[DATACENTER_KEY],
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
recurse,
|
||||
}}
|
||||
};
|
||||
return request`
|
||||
DELETE /v1/kv/${keyToArray(data[SLUG_KEY])}?${params}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Adapter from './application';
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
return request`
|
||||
|
82
ui-v2/app/adapters/nspace.js
Normal file
82
ui-v2/app/adapters/nspace.js
Normal file
@ -0,0 +1,82 @@
|
||||
import Adapter from './application';
|
||||
import { SLUG_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// namespaces aren't categorized by datacenter, therefore no dc
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { index }) {
|
||||
return request`
|
||||
GET /v1/namespaces
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an name');
|
||||
}
|
||||
return request`
|
||||
GET /v1/namespace/${id}
|
||||
|
||||
${{ index }}
|
||||
`;
|
||||
},
|
||||
requestForCreateRecord: function(request, serialized, data) {
|
||||
return request`
|
||||
PUT /v1/namespace/${data[SLUG_KEY]}
|
||||
|
||||
${{
|
||||
Name: serialized.Name,
|
||||
Description: serialized.Description,
|
||||
ACLs: {
|
||||
PolicyDefaults: serialized.ACLs.PolicyDefaults.map(item => ({ ID: item.ID })),
|
||||
RoleDefaults: serialized.ACLs.RoleDefaults.map(item => ({ ID: item.ID })),
|
||||
},
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
return request`
|
||||
PUT /v1/namespace/${data[SLUG_KEY]}
|
||||
|
||||
${{
|
||||
Description: serialized.Description,
|
||||
ACLs: {
|
||||
PolicyDefaults: serialized.ACLs.PolicyDefaults.map(item => ({ ID: item.ID })),
|
||||
RoleDefaults: serialized.ACLs.RoleDefaults.map(item => ({ ID: item.ID })),
|
||||
},
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
return request`
|
||||
DELETE /v1/namespace/${data[SLUG_KEY]}
|
||||
`;
|
||||
},
|
||||
requestForAuthorize: function(request, { dc, ns, index }) {
|
||||
return request`
|
||||
POST /v1/internal/acl/authorize?${{ dc, ns, index }}
|
||||
|
||||
${[
|
||||
{
|
||||
Resource: 'operator',
|
||||
Access: 'write',
|
||||
},
|
||||
]}
|
||||
`;
|
||||
},
|
||||
authorize: function(store, type, id, snapshot) {
|
||||
return this.request(
|
||||
function(adapter, request, serialized, unserialized) {
|
||||
return adapter.requestForAuthorize(request, serialized, unserialized);
|
||||
},
|
||||
function(serializer, respond, serialized, unserialized) {
|
||||
// Completely skip the serializer here
|
||||
return respond(function(headers, body) {
|
||||
return body;
|
||||
});
|
||||
},
|
||||
snapshot,
|
||||
type.modelName
|
||||
);
|
||||
},
|
||||
});
|
@ -1,29 +1,41 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import Adapter from './application';
|
||||
|
||||
import { SLUG_KEY } from 'consul-ui/models/policy';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
||||
return request`
|
||||
GET /v1/acl/policies?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/acl/policy/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForCreateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/policy?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/policy?${params}
|
||||
|
||||
${{
|
||||
Name: serialized.Name,
|
||||
@ -34,8 +46,12 @@ export default Adapter.extend({
|
||||
`;
|
||||
},
|
||||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/policy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/policy/${data[SLUG_KEY]}?${params}
|
||||
|
||||
${{
|
||||
Name: serialized.Name,
|
||||
@ -46,8 +62,12 @@ export default Adapter.extend({
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
DELETE /v1/acl/policy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
DELETE /v1/acl/policy/${data[SLUG_KEY]}?${params}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,13 +1,17 @@
|
||||
import Adapter from './application';
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/catalog/connect/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,29 +1,41 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import Adapter from './application';
|
||||
|
||||
import { SLUG_KEY } from 'consul-ui/models/role';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
||||
return request`
|
||||
GET /v1/acl/roles?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/acl/role/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForCreateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/role?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/role?${params}
|
||||
|
||||
${{
|
||||
Name: serialized.Name,
|
||||
@ -35,8 +47,12 @@ export default Adapter.extend({
|
||||
`;
|
||||
},
|
||||
requestForUpdateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/role/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/role/${data[SLUG_KEY]}?${params}
|
||||
|
||||
${{
|
||||
Name: serialized.Name,
|
||||
@ -48,8 +64,12 @@ export default Adapter.extend({
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
DELETE /v1/acl/role/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
DELETE /v1/acl/role/${data[SLUG_KEY]}?${params}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,20 +1,27 @@
|
||||
import Adapter from './application';
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index }) {
|
||||
requestForQuery: function(request, { dc, ns, index }) {
|
||||
return request`
|
||||
GET /v1/internal/ui/services?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/health/service/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,31 +1,44 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { SLUG_KEY } from 'consul-ui/models/session';
|
||||
import Adapter from './application';
|
||||
|
||||
import { SLUG_KEY } from 'consul-ui/models/session';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
requestForQuery: function(request, { dc, index, id }) {
|
||||
requestForQuery: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/session/node/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/session/info/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/session/destroy/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/session/destroy/${data[SLUG_KEY]}?${params}
|
||||
`;
|
||||
},
|
||||
});
|
||||
|
@ -1,31 +1,44 @@
|
||||
import Adapter, { DATACENTER_QUERY_PARAM as API_DATACENTER_KEY } from './application';
|
||||
import Adapter from './application';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import { SLUG_KEY } from 'consul-ui/models/token';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
// TODO: Update to use this.formatDatacenter()
|
||||
export default Adapter.extend({
|
||||
store: service('store'),
|
||||
|
||||
requestForQuery: function(request, { dc, index, role, policy }) {
|
||||
requestForQuery: function(request, { dc, ns, index, role, policy }) {
|
||||
return request`
|
||||
GET /v1/acl/tokens?${{ role, policy, dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForQueryRecord: function(request, { dc, index, id }) {
|
||||
requestForQueryRecord: function(request, { dc, ns, index, id }) {
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
return request`
|
||||
GET /v1/acl/token/${id}?${{ dc }}
|
||||
|
||||
${{ index }}
|
||||
${{
|
||||
...this.formatNspace(ns),
|
||||
index,
|
||||
}}
|
||||
`;
|
||||
},
|
||||
requestForCreateRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/token?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/token?${params}
|
||||
|
||||
${{
|
||||
Description: serialized.Description,
|
||||
@ -44,14 +57,19 @@ export default Adapter.extend({
|
||||
// If a token has Rules, use the old API
|
||||
if (typeof data['Rules'] !== 'undefined') {
|
||||
// https://www.consul.io/api/acl/legacy.html#update-acl-token
|
||||
// as we are using the old API we don't need to specify a nspace
|
||||
return request`
|
||||
PUT /v1/acl/update?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/update?${this.formatDatacenter(data[DATACENTER_KEY])}
|
||||
|
||||
${serialized}
|
||||
`;
|
||||
}
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/token/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
PUT /v1/acl/token/${data[SLUG_KEY]}?${params}
|
||||
|
||||
${{
|
||||
Description: serialized.Description,
|
||||
@ -63,8 +81,12 @@ export default Adapter.extend({
|
||||
`;
|
||||
},
|
||||
requestForDeleteRecord: function(request, serialized, data) {
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
DELETE /v1/acl/token/${data[SLUG_KEY]}?${{ [API_DATACENTER_KEY]: data[DATACENTER_KEY] }}
|
||||
DELETE /v1/acl/token/${data[SLUG_KEY]}?${params}
|
||||
`;
|
||||
},
|
||||
requestForSelf: function(request, serialized, { dc, index, secret }) {
|
||||
@ -77,15 +99,18 @@ export default Adapter.extend({
|
||||
${{ index }}
|
||||
`;
|
||||
},
|
||||
requestForCloneRecord: function(request, serialized, unserialized) {
|
||||
requestForCloneRecord: function(request, serialized, data) {
|
||||
// this uses snapshots
|
||||
const id = unserialized[SLUG_KEY];
|
||||
const dc = unserialized[DATACENTER_KEY];
|
||||
const id = data[SLUG_KEY];
|
||||
if (typeof id === 'undefined') {
|
||||
throw new Error('You must specify an id');
|
||||
}
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return request`
|
||||
PUT /v1/acl/token/${id}/clone?${{ [API_DATACENTER_KEY]: dc }}
|
||||
PUT /v1/acl/token/${id}/clone?${params}
|
||||
`;
|
||||
},
|
||||
// TODO: self doesn't get passed a snapshot right now
|
||||
@ -94,11 +119,11 @@ export default Adapter.extend({
|
||||
// plus we can't create Snapshots as they are private, see services/store.js
|
||||
self: function(store, type, id, unserialized) {
|
||||
return this.request(
|
||||
function(adapter, request, serialized, unserialized) {
|
||||
return adapter.requestForSelf(request, serialized, unserialized);
|
||||
function(adapter, request, serialized, data) {
|
||||
return adapter.requestForSelf(request, serialized, data);
|
||||
},
|
||||
function(serializer, respond, serialized, unserialized) {
|
||||
return serializer.respondForQueryRecord(respond, serialized, unserialized);
|
||||
function(serializer, respond, serialized, data) {
|
||||
return serializer.respondForQueryRecord(respond, serialized, data);
|
||||
},
|
||||
unserialized,
|
||||
type.modelName
|
||||
@ -106,16 +131,18 @@ export default Adapter.extend({
|
||||
},
|
||||
clone: function(store, type, id, snapshot) {
|
||||
return this.request(
|
||||
function(adapter, request, serialized, unserialized) {
|
||||
return adapter.requestForCloneRecord(request, serialized, unserialized);
|
||||
function(adapter, request, serialized, data) {
|
||||
return adapter.requestForCloneRecord(request, serialized, data);
|
||||
},
|
||||
function(serializer, respond, serialized, unserialized) {
|
||||
(serializer, respond, serialized, data) => {
|
||||
// here we just have to pass through the dc (like when querying)
|
||||
// eventually the id is created with this dc value and the id talen from the
|
||||
// eventually the id is created with this dc value and the id taken from the
|
||||
// json response of `acls/token/*/clone`
|
||||
return serializer.respondForQueryRecord(respond, {
|
||||
[API_DATACENTER_KEY]: unserialized[SLUG_KEY],
|
||||
});
|
||||
const params = {
|
||||
...this.formatDatacenter(data[DATACENTER_KEY]),
|
||||
...this.formatNspace(data[NSPACE_KEY]),
|
||||
};
|
||||
return serializer.respondForQueryRecord(respond, params);
|
||||
},
|
||||
snapshot,
|
||||
type.modelName
|
||||
|
134
ui-v2/app/components/aria-menu.js
Normal file
134
ui-v2/app/components/aria-menu.js
Normal file
@ -0,0 +1,134 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set } from '@ember/object';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
const TAB = 9;
|
||||
const ENTER = 13;
|
||||
const ESC = 27;
|
||||
const SPACE = 32;
|
||||
const END = 35;
|
||||
const HOME = 36;
|
||||
const ARROW_UP = 38;
|
||||
const ARROW_DOWN = 40;
|
||||
|
||||
const keys = {
|
||||
vertical: {
|
||||
[ARROW_DOWN]: function($items, i = -1) {
|
||||
return (i + 1) % $items.length;
|
||||
},
|
||||
[ARROW_UP]: function($items, i = 0) {
|
||||
if (i === 0) {
|
||||
return $items.length - 1;
|
||||
} else {
|
||||
return i - 1;
|
||||
}
|
||||
},
|
||||
[HOME]: function($items, i) {
|
||||
return 0;
|
||||
},
|
||||
[END]: function($items, i) {
|
||||
return $items.length - 1;
|
||||
},
|
||||
},
|
||||
horizontal: {},
|
||||
};
|
||||
const COMPONENT_ID = 'component-aria-menu-';
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
dom: service('dom'),
|
||||
guid: '',
|
||||
expanded: false,
|
||||
direction: 'vertical',
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
set(this, 'guid', this.dom.guid(this));
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
// TODO: How do you detect whether the children have changed?
|
||||
// For now we know that these elements exist and never change
|
||||
this.$menu = this.dom.element(`#${COMPONENT_ID}menu-${this.guid}`);
|
||||
const labelledBy = this.$menu.getAttribute('aria-labelledby');
|
||||
this.$trigger = this.dom.element(`#${labelledBy}`);
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
},
|
||||
actions: {
|
||||
keypress: function(e) {
|
||||
// If the event is from the trigger and its not an opening/closing
|
||||
// key then don't do anything
|
||||
if (![ENTER, SPACE, ARROW_UP, ARROW_DOWN].includes(e.keyCode)) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
// Also we may do this but not need it if we return early below
|
||||
// although once we add support for [A-Za-z] it unlikely we won't use
|
||||
// the keypress
|
||||
// ^menuitem supports menuitemradio and menuitemcheckbox
|
||||
// TODO: We need to use > somehow here so we don't select submenus
|
||||
const $items = [...this.dom.elements('[role^="menuitem"]', this.$menu)];
|
||||
if (!this.expanded) {
|
||||
this.$trigger.dispatchEvent(new MouseEvent('click'));
|
||||
if (e.keyCode === ENTER || e.keyCode === SPACE) {
|
||||
$items[0].focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// this will prevent anything happening if you haven't pushed a
|
||||
// configurable key
|
||||
if (typeof keys[this.direction][e.keyCode] === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const $focused = this.dom.element('[role="menuitem"]:focus', this.$menu);
|
||||
let i;
|
||||
if ($focused) {
|
||||
i = $items.findIndex(function($item) {
|
||||
return $item === $focused;
|
||||
});
|
||||
}
|
||||
const $next = $items[keys[this.direction][e.keyCode]($items, i)];
|
||||
$next.focus();
|
||||
},
|
||||
// TODO: The argument here needs to change to an event
|
||||
// see toggle-button.change
|
||||
change: function(open) {
|
||||
if (open) {
|
||||
this.actions.open.apply(this, []);
|
||||
} else {
|
||||
this.actions.close.apply(this, []);
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
this._listeners.remove();
|
||||
set(this, 'expanded', false);
|
||||
// TODO: Find a better way to do this without using next
|
||||
// This is needed so when you press shift tab to leave the menu
|
||||
// and go to the previous button, it doesn't focus the trigger for
|
||||
// the menu itself
|
||||
next(() => {
|
||||
this.$trigger.removeAttribute('tabindex');
|
||||
});
|
||||
},
|
||||
open: function(e) {
|
||||
set(this, 'expanded', true);
|
||||
// Take the trigger out of the tabbing whilst the menu is open
|
||||
this.$trigger.setAttribute('tabindex', '-1');
|
||||
this._listeners.add(this.dom.document(), {
|
||||
keydown: e => {
|
||||
// Keep focus on the trigger when you close via ESC
|
||||
if (e.keyCode === ESC) {
|
||||
this.$trigger.focus();
|
||||
}
|
||||
if (e.keyCode === TAB || e.keyCode === ESC) {
|
||||
this.$trigger.dispatchEvent(new MouseEvent('click'));
|
||||
return;
|
||||
}
|
||||
this.actions.keypress.apply(this, [e]);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
@ -56,7 +56,7 @@ export default Component.extend(SlotsMixin, WithListeners, {
|
||||
},
|
||||
open: function() {
|
||||
if (!get(this, 'allOptions.closed')) {
|
||||
set(this, 'allOptions', this.repo.findAllByDatacenter(this.dc));
|
||||
set(this, 'allOptions', this.repo.findAllByDatacenter(this.dc, this.nspace));
|
||||
}
|
||||
},
|
||||
save: function(item, items, success = function() {}) {
|
||||
|
@ -1,7 +0,0 @@
|
||||
import Component from '@ember/component';
|
||||
import WithClickOutside from 'consul-ui/mixins/click-outside';
|
||||
|
||||
export default Component.extend(WithClickOutside, {
|
||||
tagName: 'ul',
|
||||
onchange: function() {},
|
||||
});
|
@ -1,23 +1,26 @@
|
||||
import Component from '@ember/component';
|
||||
import { get, set } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
dom: service('dom'),
|
||||
isDropdownVisible: false,
|
||||
didInsertElement: function() {
|
||||
this.dom.root().classList.remove('template-with-vertical-menu');
|
||||
},
|
||||
// TODO: Right now this is the only place where we need permissions
|
||||
// but we are likely to need it elsewhere, so probably need a nice helper
|
||||
canManageNspaces: computed('permissions', function() {
|
||||
return (
|
||||
typeof (this.permissions || []).find(function(item) {
|
||||
return item.Resource === 'operator' && item.Access === 'write' && item.Allow;
|
||||
}) !== 'undefined'
|
||||
);
|
||||
}),
|
||||
actions: {
|
||||
dropdown: function(e) {
|
||||
if (get(this, 'dcs.length') > 0) {
|
||||
set(this, 'isDropdownVisible', !this.isDropdownVisible);
|
||||
}
|
||||
},
|
||||
change: function(e) {
|
||||
const dom = this.dom;
|
||||
const win = dom.viewport();
|
||||
const $root = dom.root();
|
||||
const $body = dom.element('body');
|
||||
const win = this.dom.viewport();
|
||||
const $root = this.dom.root();
|
||||
const $body = this.dom.element('body');
|
||||
if (e.target.checked) {
|
||||
$root.classList.add('template-with-vertical-menu');
|
||||
$body.style.height = $root.style.height = win.innerHeight + 'px';
|
||||
|
@ -73,12 +73,10 @@ export default ChildSelectorComponent.extend({
|
||||
}
|
||||
// potentially the item could change between load, so we don't check
|
||||
// anything to see if its already loaded here
|
||||
const repo = this.repo;
|
||||
// TODO: Temporarily add dc here, will soon be serialized onto the policy itself
|
||||
const dc = this.dc;
|
||||
const slugKey = repo.getSlugKey();
|
||||
const slugKey = this.repo.getSlugKey();
|
||||
const slug = get(value, slugKey);
|
||||
updateArrayObject(items, repo.findBySlug(slug, dc), slugKey, slug);
|
||||
updateArrayObject(items, this.repo.findBySlug(slug, this.dc, this.nspace), slugKey, slug);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
6
ui-v2/app/components/popover-menu.js
Normal file
6
ui-v2/app/components/popover-menu.js
Normal file
@ -0,0 +1,6 @@
|
||||
import Component from '@ember/component';
|
||||
import Slotted from 'block-slots';
|
||||
|
||||
export default Component.extend(Slotted, {
|
||||
tagName: '',
|
||||
});
|
54
ui-v2/app/components/toggle-button.js
Normal file
54
ui-v2/app/components/toggle-button.js
Normal file
@ -0,0 +1,54 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { set } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
dom: service('dom'),
|
||||
// TODO(octane): Remove when we can move to glimmer components
|
||||
// so we aren't using ember-test-selectors
|
||||
// supportsDataTestProperties: true,
|
||||
// the above doesn't seem to do anything so still need to find a way
|
||||
// to pass through data-test-properties
|
||||
tagName: '',
|
||||
// TODO: reserved for the moment but we don't need it yet
|
||||
onblur: null,
|
||||
onchange: function() {},
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.guid = this.dom.guid(this);
|
||||
this._listeners = this.dom.listeners();
|
||||
},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
// TODO(octane): move to ref
|
||||
set(this, 'input', this.dom.element(`#toggle-button-${this.guid}`));
|
||||
set(this, 'label', this.input.nextElementSibling);
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners.remove();
|
||||
},
|
||||
actions: {
|
||||
click: function(e) {
|
||||
e.preventDefault();
|
||||
this.input.checked = !this.input.checked;
|
||||
this.actions.change.apply(this, [e]);
|
||||
},
|
||||
change: function(e) {
|
||||
if (this.input.checked) {
|
||||
// default onblur event
|
||||
this._listeners.remove();
|
||||
this._listeners.add(this.dom.document(), 'click', e => {
|
||||
if (this.dom.isOutside(this.label, e.target)) {
|
||||
this.input.checked = false;
|
||||
// TODO: This should be an event
|
||||
this.onchange(this.input.checked);
|
||||
this._listeners.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
// TODO: This should be an event
|
||||
this.onchange(this.input.checked);
|
||||
},
|
||||
},
|
||||
});
|
@ -9,17 +9,27 @@ export default Controller.extend({
|
||||
this.form = this.builder.form('intention');
|
||||
},
|
||||
setProperties: function(model) {
|
||||
const sourceName = get(model.item, 'SourceName');
|
||||
const destinationName = get(model.item, 'DestinationName');
|
||||
let source = model.items.findBy('Name', sourceName);
|
||||
let destination = model.items.findBy('Name', destinationName);
|
||||
let source = model.services.findBy('Name', model.item.SourceName);
|
||||
|
||||
if (!source) {
|
||||
source = { Name: sourceName };
|
||||
model.items = [source].concat(model.items);
|
||||
source = { Name: model.item.SourceName };
|
||||
model.services = [source].concat(model.services);
|
||||
}
|
||||
let destination = model.services.findBy('Name', model.item.DestinationName);
|
||||
if (!destination) {
|
||||
destination = { Name: destinationName };
|
||||
model.items = [destination].concat(model.items);
|
||||
destination = { Name: model.item.DestinationName };
|
||||
model.services = [destination].concat(model.services);
|
||||
}
|
||||
|
||||
let sourceNS = model.nspaces.findBy('Name', model.item.SourceNS);
|
||||
if (!sourceNS) {
|
||||
sourceNS = { Name: model.item.SourceNS };
|
||||
model.nspaces = [sourceNS].concat(model.nspaces);
|
||||
}
|
||||
let destinationNS = model.nspaces.findBy('Name', model.item.DestinationNS);
|
||||
if (!destinationNS) {
|
||||
destinationNS = { Name: model.item.DestinationNS };
|
||||
model.nspaces = [destinationNS].concat(model.nspaces);
|
||||
}
|
||||
this._super({
|
||||
...model,
|
||||
@ -27,6 +37,8 @@ export default Controller.extend({
|
||||
item: this.form.setData(model.item).getData(),
|
||||
SourceName: source,
|
||||
DestinationName: destination,
|
||||
SourceNS: sourceNS,
|
||||
DestinationNS: destinationNS,
|
||||
},
|
||||
});
|
||||
},
|
||||
@ -35,34 +47,25 @@ export default Controller.extend({
|
||||
return template.replace(/{{term}}/g, term);
|
||||
},
|
||||
isUnique: function(term) {
|
||||
return !this.items.findBy('Name', term);
|
||||
return !this.services.findBy('Name', term);
|
||||
},
|
||||
change: function(e, value, item) {
|
||||
const event = this.dom.normalizeEvent(e, value);
|
||||
const form = this.form;
|
||||
const target = event.target;
|
||||
|
||||
let name;
|
||||
let selected;
|
||||
let match;
|
||||
let name, selected, match;
|
||||
switch (target.name) {
|
||||
case 'SourceName':
|
||||
case 'DestinationName':
|
||||
case 'SourceNS':
|
||||
case 'DestinationNS':
|
||||
name = selected = target.value;
|
||||
// Names can be selected Service EmberObjects or typed in strings
|
||||
// if its not a string, use the `Name` from the Service EmberObject
|
||||
if (typeof name !== 'string') {
|
||||
name = get(target.value, 'Name');
|
||||
}
|
||||
// see if the name is already in the list
|
||||
match = this.items.filterBy('Name', name);
|
||||
if (match.length === 0) {
|
||||
// if its not make a new 'fake' Service that doesn't exist yet
|
||||
// and add it to the possible services to make an intention between
|
||||
selected = { Name: name };
|
||||
const items = [selected].concat(this.items.toArray());
|
||||
set(this, 'items', items);
|
||||
}
|
||||
// mutate the value with the string name
|
||||
// which will be handled by the form
|
||||
target.value = name;
|
||||
@ -71,6 +74,23 @@ export default Controller.extend({
|
||||
// the current selection
|
||||
// basically the difference between
|
||||
// `item.DestinationName` and just `DestinationName`
|
||||
// see if the name is already in the list
|
||||
match = this.services.filterBy('Name', name);
|
||||
if (match.length === 0) {
|
||||
// if its not make a new 'fake' Service that doesn't exist yet
|
||||
// and add it to the possible services to make an intention between
|
||||
selected = { Name: name };
|
||||
switch (target.name) {
|
||||
case 'SourceName':
|
||||
case 'DestinationName':
|
||||
set(this, 'services', [selected].concat(this.services.toArray()));
|
||||
break;
|
||||
case 'SourceNS':
|
||||
case 'DestinationNS':
|
||||
set(this, 'nspaces', [selected].concat(this.nspaces.toArray()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
set(this, target.name, selected);
|
||||
break;
|
||||
}
|
||||
|
2
ui-v2/app/controllers/dc/nspaces/create.js
Normal file
2
ui-v2/app/controllers/dc/nspaces/create.js
Normal file
@ -0,0 +1,2 @@
|
||||
import Controller from './edit';
|
||||
export default Controller.extend();
|
38
ui-v2/app/controllers/dc/nspaces/edit.js
Normal file
38
ui-v2/app/controllers/dc/nspaces/edit.js
Normal file
@ -0,0 +1,38 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
export default Controller.extend({
|
||||
dom: service('dom'),
|
||||
builder: service('form'),
|
||||
init: function() {
|
||||
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);
|
||||
} catch (err) {
|
||||
const target = event.target;
|
||||
switch (target.name) {
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
23
ui-v2/app/controllers/dc/nspaces/index.js
Normal file
23
ui-v2/app/controllers/dc/nspaces/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { get, computed } from '@ember/object';
|
||||
|
||||
import WithEventSource from 'consul-ui/mixins/with-event-source';
|
||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||
export default Controller.extend(WithEventSource, WithSearching, {
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
},
|
||||
},
|
||||
init: function() {
|
||||
this.searchParams = {
|
||||
nspace: 's',
|
||||
};
|
||||
this._super(...arguments);
|
||||
},
|
||||
searchable: computed('items.[]', function() {
|
||||
return get(this, 'searchables.nspace')
|
||||
.add(this.items)
|
||||
.search(this.terms);
|
||||
}),
|
||||
});
|
@ -1,5 +1,61 @@
|
||||
import config from './config/environment';
|
||||
export default function(str) {
|
||||
const user = window.localStorage.getItem(str);
|
||||
return user !== null ? user : config[str];
|
||||
}
|
||||
import _config from './config/environment';
|
||||
const doc = document;
|
||||
const getDevEnvVars = function() {
|
||||
return doc.cookie
|
||||
.split(';')
|
||||
.filter(item => item !== '')
|
||||
.map(item => item.trim().split('='));
|
||||
};
|
||||
const getUserEnvVar = function(str) {
|
||||
return window.localStorage.getItem(str);
|
||||
};
|
||||
// TODO: Look at `services/client` for pulling
|
||||
// HTTP headers in here so we can let things be controlled
|
||||
// via HTTP proxies, for example turning off blocking
|
||||
// queries if its a busy cluster
|
||||
// const getOperatorEnvVars = function() {}
|
||||
|
||||
// TODO: Not necessarily here but the entire app should
|
||||
// use the `env` export not the `default` one
|
||||
// but we might also change the name of this file, so wait for that first
|
||||
export const env = function(str) {
|
||||
let user = null;
|
||||
switch (str) {
|
||||
case 'CONSUL_UI_DISABLE_REALTIME':
|
||||
case 'CONSUL_UI_DISABLE_ANCHOR_SELECTION':
|
||||
case 'CONSUL_UI_REALTIME_RUNNER':
|
||||
user = getUserEnvVar(str);
|
||||
break;
|
||||
}
|
||||
// We use null here instead of an undefined check
|
||||
// as localStorage will return null if not set
|
||||
return user !== null ? user : _config[str];
|
||||
};
|
||||
export const config = function(key) {
|
||||
let $;
|
||||
switch (_config.environment) {
|
||||
case 'development':
|
||||
case 'staging':
|
||||
case 'test':
|
||||
$ = getDevEnvVars().reduce(function(prev, [key, value]) {
|
||||
const val = !!JSON.parse(String(value).toLowerCase());
|
||||
switch (key) {
|
||||
case 'CONSUL_ACLS_ENABLE':
|
||||
prev['CONSUL_ACLS_ENABLED'] = val;
|
||||
break;
|
||||
case 'CONSUL_NSPACES_ENABLE':
|
||||
prev['CONSUL_NSPACES_ENABLED'] = val;
|
||||
break;
|
||||
default:
|
||||
prev[key] = value;
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
if (typeof $[key] !== 'undefined') {
|
||||
return $[key];
|
||||
}
|
||||
break;
|
||||
}
|
||||
return _config[key];
|
||||
};
|
||||
export default env;
|
||||
|
9
ui-v2/app/forms/nspace.js
Normal file
9
ui-v2/app/forms/nspace.js
Normal file
@ -0,0 +1,9 @@
|
||||
import validations from 'consul-ui/validations/nspace';
|
||||
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)
|
||||
.add(container.form('policy'))
|
||||
.add(container.form('role'));
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
import $ from 'consul-ui/config/environment';
|
||||
import { config } from 'consul-ui/env';
|
||||
// TODO: env actually uses config values not env values
|
||||
// see `app/env` for the renaming TODO's also
|
||||
export function env([name, def = ''], hash) {
|
||||
return $[name] != null ? $[name] : def;
|
||||
return config(name) != null ? config(name) : def;
|
||||
}
|
||||
|
||||
export default helper(env);
|
||||
|
39
ui-v2/app/helpers/href-mut.js
Normal file
39
ui-v2/app/helpers/href-mut.js
Normal file
@ -0,0 +1,39 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hrefTo } from 'consul-ui/helpers/href-to';
|
||||
|
||||
const getRouteParams = function(route, params = {}) {
|
||||
return route.paramNames.map(function(item) {
|
||||
if (typeof params[item] !== 'undefined') {
|
||||
return params[item];
|
||||
}
|
||||
return route.params[item];
|
||||
});
|
||||
};
|
||||
export default Helper.extend({
|
||||
router: service('router'),
|
||||
compute([params], hash) {
|
||||
let current = this.router.currentRoute;
|
||||
let parent;
|
||||
let atts = getRouteParams(current, params);
|
||||
// walk up the entire route/s replacing any instances
|
||||
// of the specified params with the values specified
|
||||
while ((parent = current.parent)) {
|
||||
atts = atts.concat(getRouteParams(parent, params));
|
||||
current = parent;
|
||||
}
|
||||
let route = this.router.currentRoute.name;
|
||||
// TODO: this is specific to consul/nspaces
|
||||
// 'ideally' we could try and do this elsewhere
|
||||
// not super important though.
|
||||
// This will turn an URL that has no nspace (/ui/dc-1/nodes) into one
|
||||
// that does have a namespace (/ui/~nspace/dc-1/nodes) if you href-mut with
|
||||
// a nspace parameter
|
||||
if (typeof params.nspace !== 'undefined' && !route.startsWith('nspace.')) {
|
||||
route = `nspace.${route}`;
|
||||
atts.push(params.nspace);
|
||||
}
|
||||
//
|
||||
return hrefTo(this, this.router, [route, ...atts.reverse()], hash);
|
||||
},
|
||||
});
|
@ -1,29 +1,42 @@
|
||||
// This helper requires `ember-href-to` for the moment at least
|
||||
// It's similar code but allows us to check on the type of route
|
||||
// (dynamic or wildcard) and encode or not depending on the type
|
||||
import { inject as service } from '@ember/service';
|
||||
import Helper from '@ember/component/helper';
|
||||
import { hrefTo } from 'ember-href-to/helpers/href-to';
|
||||
import { hrefTo as _hrefTo } from 'ember-href-to/helpers/href-to';
|
||||
|
||||
import wildcard from 'consul-ui/utils/routing/wildcard';
|
||||
|
||||
import { routes } from 'consul-ui/router';
|
||||
|
||||
const isWildcard = wildcard(routes);
|
||||
export const hrefTo = function(owned, router, [targetRouteName, ...rest], namedArgs) {
|
||||
if (isWildcard(targetRouteName)) {
|
||||
rest = rest.map(function(item, i) {
|
||||
return item
|
||||
.split('/')
|
||||
.map(encodeURIComponent)
|
||||
.join('/');
|
||||
});
|
||||
}
|
||||
if (namedArgs.params) {
|
||||
return _hrefTo(owned, namedArgs.params);
|
||||
} else {
|
||||
// we don't check to see if nspaces are enabled here as routes
|
||||
// with beginning with 'nspace' only exist if nspaces are enabled
|
||||
|
||||
// this globally converts non-nspaced href-to's to nspace aware
|
||||
// href-to's only if you are within a namespace
|
||||
if (router.currentRouteName.startsWith('nspace.') && targetRouteName.startsWith('dc.')) {
|
||||
targetRouteName = `nspace.${targetRouteName}`;
|
||||
}
|
||||
return _hrefTo(owned, [targetRouteName, ...rest]);
|
||||
}
|
||||
};
|
||||
|
||||
export default Helper.extend({
|
||||
compute([targetRouteName, ...rest], namedArgs) {
|
||||
if (isWildcard(targetRouteName)) {
|
||||
rest = rest.map(function(item, i) {
|
||||
return item
|
||||
.split('/')
|
||||
.map(encodeURIComponent)
|
||||
.join('/');
|
||||
});
|
||||
}
|
||||
if (namedArgs.params) {
|
||||
return hrefTo(this, namedArgs.params);
|
||||
} else {
|
||||
return hrefTo(this, [targetRouteName, ...rest]);
|
||||
}
|
||||
router: service('router'),
|
||||
compute(params, hash) {
|
||||
return hrefTo(this, this.router, params, hash);
|
||||
},
|
||||
});
|
||||
|
@ -6,8 +6,11 @@ import { observer } from '@ember/object';
|
||||
|
||||
export default Helper.extend({
|
||||
router: service('router'),
|
||||
compute(params) {
|
||||
return this.router.isActive(...params);
|
||||
compute([targetRouteName, ...rest]) {
|
||||
if (this.router.currentRouteName.startsWith('nspace.') && targetRouteName.startsWith('dc.')) {
|
||||
targetRouteName = `nspace.${targetRouteName}`;
|
||||
}
|
||||
return this.router.isActive(...[targetRouteName, ...rest]);
|
||||
},
|
||||
onURLChange: observer('router.currentURL', function() {
|
||||
this.recompute();
|
||||
|
@ -6,6 +6,7 @@ import token from 'consul-ui/forms/token';
|
||||
import policy from 'consul-ui/forms/policy';
|
||||
import role from 'consul-ui/forms/role';
|
||||
import intention from 'consul-ui/forms/intention';
|
||||
import nspace from 'consul-ui/forms/nspace';
|
||||
|
||||
export function initialize(application) {
|
||||
// Service-less injection using private properties at a per-project level
|
||||
@ -17,6 +18,7 @@ export function initialize(application) {
|
||||
policy: policy,
|
||||
role: role,
|
||||
intention: intention,
|
||||
nspace: nspace,
|
||||
};
|
||||
FormBuilder.reopen({
|
||||
form: function(name) {
|
||||
|
61
ui-v2/app/initializers/nspace.js
Normal file
61
ui-v2/app/initializers/nspace.js
Normal file
@ -0,0 +1,61 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { config } from 'consul-ui/env';
|
||||
|
||||
import { routes } from 'consul-ui/router';
|
||||
import flat from 'flat';
|
||||
|
||||
let initialize = function() {};
|
||||
Route.reopen(
|
||||
['modelFor', 'transitionTo', 'replaceWith', 'paramsFor'].reduce(function(prev, item) {
|
||||
prev[item] = function(routeName, ...rest) {
|
||||
const isNspaced = this.routeName.startsWith('nspace.');
|
||||
if (routeName === 'nspace') {
|
||||
if (isNspaced || this.routeName === 'nspace') {
|
||||
return this._super(...arguments);
|
||||
} else {
|
||||
return {
|
||||
nspace: '~',
|
||||
};
|
||||
}
|
||||
}
|
||||
if (isNspaced && routeName.startsWith('dc')) {
|
||||
return this._super(...[`nspace.${routeName}`, ...rest]);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
};
|
||||
return prev;
|
||||
}, {})
|
||||
);
|
||||
if (config('CONSUL_NSPACES_ENABLED')) {
|
||||
const dotRe = /\./g;
|
||||
initialize = function(container) {
|
||||
const all = Object.keys(flat(routes))
|
||||
.filter(function(item) {
|
||||
return item.startsWith('dc');
|
||||
})
|
||||
.map(function(item) {
|
||||
return item.replace('._options.path', '').replace(dotRe, '/');
|
||||
});
|
||||
all.forEach(function(item) {
|
||||
let route = container.resolveRegistration(`route:${item}`);
|
||||
if (!route) {
|
||||
item = `${item}/index`;
|
||||
route = container.resolveRegistration(`route:${item}`);
|
||||
}
|
||||
route.reopen({
|
||||
templateName: item
|
||||
.replace('/root-create', '/create')
|
||||
.replace('/create', '/edit')
|
||||
.replace('/folder', '/index'),
|
||||
});
|
||||
container.register(`route:nspace/${item}`, route);
|
||||
const controller = container.resolveRegistration(`controller:${item}`);
|
||||
if (controller) {
|
||||
container.register(`controller:nspace/${item}`, controller);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
export default {
|
||||
initialize,
|
||||
};
|
@ -9,6 +9,7 @@ import node from 'consul-ui/search/filters/node';
|
||||
import nodeService from 'consul-ui/search/filters/node/service';
|
||||
import serviceNode from 'consul-ui/search/filters/service/node';
|
||||
import service from 'consul-ui/search/filters/service';
|
||||
import nspace from 'consul-ui/search/filters/nspace';
|
||||
|
||||
import filterableFactory from 'consul-ui/utils/search/filterable';
|
||||
const filterable = filterableFactory();
|
||||
@ -27,6 +28,7 @@ export function initialize(application) {
|
||||
serviceInstance: serviceNode(filterable),
|
||||
nodeservice: nodeService(filterable),
|
||||
service: service(filterable),
|
||||
nspace: nspace(filterable),
|
||||
};
|
||||
Builder.reopen({
|
||||
searchable: function(name) {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import env from 'consul-ui/env';
|
||||
import env, { config } from 'consul-ui/env';
|
||||
|
||||
export function initialize(container) {
|
||||
if (env('CONSUL_UI_DISABLE_REALTIME')) {
|
||||
return;
|
||||
}
|
||||
['node', 'coordinate', 'session', 'service', 'proxy']
|
||||
.concat(config('CONSUL_NSPACES_ENABLED') ? ['nspace/enabled'] : [])
|
||||
.map(function(item) {
|
||||
// create repositories that return a promise resolving to an EventSource
|
||||
return {
|
||||
@ -76,6 +77,18 @@ export function initialize(container) {
|
||||
},
|
||||
},
|
||||
])
|
||||
.concat(
|
||||
config('CONSUL_NSPACES_ENABLED')
|
||||
? [
|
||||
{
|
||||
route: 'dc/nspaces/index',
|
||||
services: {
|
||||
repo: 'repository/nspace/enabled/event-source',
|
||||
},
|
||||
},
|
||||
]
|
||||
: []
|
||||
)
|
||||
.forEach(function(definition) {
|
||||
if (typeof definition.extend !== 'undefined') {
|
||||
// Create the class instances that we need
|
||||
@ -90,6 +103,9 @@ export function initialize(container) {
|
||||
// but hardcode this for the moment
|
||||
if (typeof definition.route !== 'undefined') {
|
||||
container.inject(`route:${definition.route}`, name, `service:${servicePath}`);
|
||||
if (config('CONSUL_NSPACES_ENABLED') && definition.route.startsWith('dc/')) {
|
||||
container.inject(`route:nspace/${definition.route}`, name, `service:${servicePath}`);
|
||||
}
|
||||
} else {
|
||||
container.inject(`service:${definition.service}`, name, `service:${servicePath}`);
|
||||
}
|
||||
|
28
ui-v2/app/instance-initializers/nspace.js
Normal file
28
ui-v2/app/instance-initializers/nspace.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { config } from 'consul-ui/env';
|
||||
export function initialize(container) {
|
||||
if (config('CONSUL_NSPACES_ENABLED')) {
|
||||
['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');
|
||||
container
|
||||
.lookup('service:dom')
|
||||
.root()
|
||||
.classList.add('has-nspaces');
|
||||
}
|
||||
// FIXME: This needs to live in its own initializer, either:
|
||||
// 1. Make it be about adding classes to the root dom node
|
||||
// 2. Make it be about config and things to do on initialization re: config
|
||||
// If we go with 1 then we need to move both this and the above nspaces class
|
||||
if (config('CONSUL_ACLS_ENABLED')) {
|
||||
container
|
||||
.lookup('service:dom')
|
||||
.root()
|
||||
.classList.add('has-acls');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
initialize,
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
// TODO: Potentially move this to dom service
|
||||
const isOutside = function(element, e, doc = document) {
|
||||
if (element) {
|
||||
const isRemoved = !e.target || !doc.contains(e.target);
|
||||
const isInside = element === e.target || element.contains(e.target);
|
||||
return !isRemoved && !isInside;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handler = function(e) {
|
||||
const el = this.element;
|
||||
if (isOutside(el, e)) {
|
||||
this.onblur(e);
|
||||
}
|
||||
};
|
||||
export default Mixin.create({
|
||||
dom: service('dom'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this.handler = handler.bind(this);
|
||||
},
|
||||
onchange: function() {},
|
||||
onblur: function() {},
|
||||
didInsertElement: function() {
|
||||
this._super(...arguments);
|
||||
const doc = this.dom.document();
|
||||
next(this, () => {
|
||||
doc.addEventListener('click', this.handler);
|
||||
});
|
||||
},
|
||||
willDestroyElement: function() {
|
||||
this._super(...arguments);
|
||||
const doc = this.dom.document();
|
||||
doc.removeEventListener('click', this.handler);
|
||||
},
|
||||
});
|
4
ui-v2/app/mixins/nspace/with-actions.js
Normal file
4
ui-v2/app/mixins/nspace/with-actions.js
Normal file
@ -0,0 +1,4 @@
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
export default Mixin.create(WithBlockingActions, {});
|
@ -1,10 +1,9 @@
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
import writable from 'consul-ui/utils/model/writable';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
const model = Model.extend({
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
Description: attr('string'),
|
||||
@ -15,8 +14,11 @@ const model = Model.extend({
|
||||
Precedence: attr('number'),
|
||||
SourceType: attr('string', { defaultValue: 'consul' }),
|
||||
Action: attr('string', { defaultValue: 'deny' }),
|
||||
// These are in the API response but up until now
|
||||
// aren't used for anything
|
||||
DefaultAddr: attr('string'),
|
||||
DefaultPort: attr('number'),
|
||||
//
|
||||
Meta: attr(),
|
||||
Datacenter: attr('string'),
|
||||
CreatedAt: attr('date'),
|
||||
@ -24,12 +26,3 @@ const model = Model.extend({
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
});
|
||||
// TODO: Remove this in favour of just specifying it in the Adapter
|
||||
export const ATTRS = writable(model, [
|
||||
'Action',
|
||||
'SourceName',
|
||||
'DestinationName',
|
||||
'SourceType',
|
||||
'Description',
|
||||
]);
|
||||
export default model;
|
||||
|
@ -22,6 +22,7 @@ export default Model.extend({
|
||||
ModifyIndex: attr('number'),
|
||||
Session: attr('string'),
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
|
||||
isFolder: computed('Key', function() {
|
||||
return isFolder(get(this, 'Key') || '');
|
||||
|
20
ui-v2/app/models/nspace.js
Normal file
20
ui-v2/app/models/nspace.js
Normal file
@ -0,0 +1,20 @@
|
||||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
|
||||
export const PRIMARY_KEY = 'Name';
|
||||
// keep this for consistency
|
||||
export const SLUG_KEY = 'Name';
|
||||
export const NSPACE_KEY = 'Namespace';
|
||||
export default Model.extend({
|
||||
[PRIMARY_KEY]: attr('string'),
|
||||
[SLUG_KEY]: attr('string'),
|
||||
|
||||
Description: attr('string', { defaultValue: '' }),
|
||||
// TODO: Is there some sort of date we can use here
|
||||
DeletedAt: attr('string'),
|
||||
ACLs: attr(undefined, function() {
|
||||
return { defaultValue: { PolicyDefaults: [], RoleDefaults: [] } };
|
||||
}),
|
||||
|
||||
SyncTime: attr('number'),
|
||||
});
|
@ -20,6 +20,7 @@ export default Model.extend({
|
||||
CreateTime: attr('date'),
|
||||
//
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
Datacenters: attr(),
|
||||
CreateIndex: attr('number'),
|
||||
ModifyIndex: attr('number'),
|
||||
|
@ -11,4 +11,6 @@ export default Model.extend({
|
||||
Node: attr('string'),
|
||||
ServiceProxy: attr(),
|
||||
SyncTime: attr('number'),
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
});
|
||||
|
@ -26,6 +26,7 @@ export default Model.extend({
|
||||
CreateTime: attr('date'),
|
||||
//
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
// TODO: Figure out whether we need this or not
|
||||
Datacenters: attr(),
|
||||
Hash: attr('string'),
|
||||
|
@ -28,6 +28,7 @@ export default Model.extend({
|
||||
ChecksWarning: attr(),
|
||||
Nodes: attr(),
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
Node: attr(),
|
||||
Service: attr(),
|
||||
Checks: attr(),
|
||||
|
@ -20,5 +20,6 @@ export default Model.extend({
|
||||
},
|
||||
}),
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
SyncTime: attr('number'),
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ export default Model.extend({
|
||||
defaultValue: '',
|
||||
}),
|
||||
Datacenter: attr('string'),
|
||||
Namespace: attr('string'),
|
||||
Local: attr('boolean'),
|
||||
Policies: attr({
|
||||
defaultValue: function() {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import EmberRouter from '@ember/routing/router';
|
||||
import config from './config/environment';
|
||||
import { config } from 'consul-ui/env';
|
||||
import walk from 'consul-ui/utils/routing/walk';
|
||||
|
||||
const Router = EmberRouter.extend({
|
||||
location: config.locationType,
|
||||
rootURL: config.rootURL,
|
||||
location: config('locationType'),
|
||||
rootURL: config('rootURL'),
|
||||
});
|
||||
export const routes = {
|
||||
// Our parent datacenter resource sets the namespace
|
||||
// for the entire application
|
||||
dc: {
|
||||
_options: { path: ':dc' },
|
||||
_options: { path: '/:dc' },
|
||||
// Services represent a consul service
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
@ -107,4 +107,19 @@ export const routes = {
|
||||
_options: { path: '/*path' },
|
||||
},
|
||||
};
|
||||
if (config('CONSUL_NSPACES_ENABLED')) {
|
||||
routes.dc.nspaces = {
|
||||
_options: { path: '/namespaces' },
|
||||
edit: {
|
||||
_options: { path: '/:name' },
|
||||
},
|
||||
create: {
|
||||
_options: { path: '/create' },
|
||||
},
|
||||
};
|
||||
routes.nspace = {
|
||||
_options: { path: '/:nspace' },
|
||||
dc: routes.dc,
|
||||
};
|
||||
}
|
||||
export default Router.map(walk(routes));
|
||||
|
@ -14,6 +14,7 @@ export default Route.extend(WithBlockingActions, {
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
},
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
repo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
actions: {
|
||||
@ -27,6 +28,7 @@ export default Route.extend(WithBlockingActions, {
|
||||
hash({
|
||||
loading: !$root.classList.contains('ember-loading'),
|
||||
dc: dc,
|
||||
nspace: this.nspacesRepo.getActive(),
|
||||
}).then(model => {
|
||||
next(() => {
|
||||
const controller = this.controllerFor('application');
|
||||
@ -81,8 +83,8 @@ export default Route.extend(WithBlockingActions, {
|
||||
error.status.toString().indexOf('5') !== 0
|
||||
? this.repo.getActive()
|
||||
: model && model.dc
|
||||
? model.dc
|
||||
: { Name: 'Error' },
|
||||
? model.dc
|
||||
: { Name: 'Error' },
|
||||
dcs: model && model.dcs ? model.dcs : [],
|
||||
})
|
||||
.then(model => {
|
||||
|
@ -1,21 +1,70 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
// TODO: We should potentially move all these nspace related things
|
||||
// up a level to application.js
|
||||
|
||||
const findActiveNspace = function(nspaces, nspace) {
|
||||
let found = nspaces.find(function(item) {
|
||||
return item.Name === nspace.Name;
|
||||
});
|
||||
if (typeof found === 'undefined') {
|
||||
// if we can't find the nspace that was saved
|
||||
// try default
|
||||
found = nspaces.find(function(item) {
|
||||
return item.Name === 'default';
|
||||
});
|
||||
// if there is no default just choose the first
|
||||
if (typeof found === 'undefined') {
|
||||
found = nspaces.firstObject;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
};
|
||||
export default Route.extend({
|
||||
repo: service('repository/dc'),
|
||||
settings: service('settings'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
settingsRepo: service('settings'),
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
const nspacesRepo = this.nspacesRepo;
|
||||
const settingsRepo = this.settingsRepo;
|
||||
return hash({
|
||||
dcs: repo.findAll(),
|
||||
}).then(function(model) {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
dc: repo.findBySlug(params.dc, model.dcs),
|
||||
},
|
||||
nspaces: nspacesRepo.findAll(),
|
||||
nspace: nspacesRepo.getActive(),
|
||||
token: settingsRepo.findBySlug('token'),
|
||||
})
|
||||
.then(function(model) {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
dc: repo.findBySlug(params.dc, model.dcs),
|
||||
// if there is only 1 namespace then use that
|
||||
// otherwise find the namespace object that corresponds
|
||||
// to the active one
|
||||
nspace:
|
||||
model.nspaces.length > 1
|
||||
? findActiveNspace(model.nspaces, model.nspace)
|
||||
: model.nspaces.firstObject,
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(function(model) {
|
||||
if (get(model, 'token.SecretID')) {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
// When disabled nspaces is [], so nspace is undefined
|
||||
permissions: nspacesRepo.authorize(params.dc, get(model, 'nspace.Name')),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return model;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
|
@ -3,11 +3,12 @@ import { get } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
export default Route.extend(WithBlockingActions, {
|
||||
router: service('router'),
|
||||
settings: service('settings'),
|
||||
feedback: service('feedback'),
|
||||
repo: service('repository/token'),
|
||||
actions: {
|
||||
authorize: function(secret) {
|
||||
authorize: function(secret, nspace) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
return this.feedback.execute(() => {
|
||||
return this.repo.self(secret, dc).then(item => {
|
||||
@ -16,6 +17,7 @@ export default Route.extend(WithBlockingActions, {
|
||||
token: {
|
||||
AccessorID: get(item, 'AccessorID'),
|
||||
SecretID: secret,
|
||||
Namespace: get(item, 'Namespace'),
|
||||
},
|
||||
})
|
||||
.then(item => {
|
||||
@ -29,7 +31,15 @@ export default Route.extend(WithBlockingActions, {
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
this.refresh();
|
||||
if (get(item, 'token.Namespace') !== nspace) {
|
||||
let routeName = this.router.currentRouteName;
|
||||
if (!routeName.startsWith('nspace')) {
|
||||
routeName = `nspace.${routeName}`;
|
||||
}
|
||||
return this.transitionTo(`${routeName}`, `~${get(item, 'token.Namespace')}`, dc);
|
||||
} else {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/acl/with-actions';
|
||||
|
||||
@ -12,8 +12,9 @@ export default Route.extend(WithAclActions, {
|
||||
this.repo.invalidate();
|
||||
},
|
||||
model: function(params) {
|
||||
this.item = this.repo.create();
|
||||
set(this.item, 'Datacenter', this.modelFor('dc').dc.Name);
|
||||
this.item = this.repo.create({
|
||||
Datacenter: this.modelFor('dc').dc.Name,
|
||||
});
|
||||
return hash({
|
||||
create: true,
|
||||
isLoading: false,
|
||||
|
@ -10,12 +10,13 @@ export default SingleRoute.extend(WithPolicyActions, {
|
||||
tokenRepo: service('repository/token'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
const tokenRepo = this.tokenRepo;
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
items: tokenRepo.findByPolicy(get(model.item, 'ID'), dc, nspace).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
|
@ -13,10 +13,12 @@ export default Route.extend(WithPolicyActions, {
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
return hash({
|
||||
...repo.status({
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter(
|
||||
this.modelFor('dc').dc.Name,
|
||||
this.modelFor('nspace').nspace.substr(1)
|
||||
),
|
||||
}),
|
||||
isLoading: false,
|
||||
});
|
||||
|
@ -10,12 +10,13 @@ export default SingleRoute.extend(WithRoleActions, {
|
||||
tokenRepo: service('repository/token'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
const tokenRepo = this.tokenRepo;
|
||||
return this._super(...arguments).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: tokenRepo.findByRole(get(model.item, 'ID'), dc).catch(function(e) {
|
||||
items: tokenRepo.findByRole(get(model.item, 'ID'), dc, nspace).catch(function(e) {
|
||||
switch (get(e, 'errors.firstObject.status')) {
|
||||
case '403':
|
||||
case '401':
|
||||
|
@ -13,10 +13,12 @@ export default Route.extend(WithRoleActions, {
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
return hash({
|
||||
...repo.status({
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter(
|
||||
this.modelFor('dc').dc.Name,
|
||||
this.modelFor('nspace').nspace.substr(1)
|
||||
),
|
||||
}),
|
||||
isLoading: false,
|
||||
});
|
||||
|
@ -24,11 +24,14 @@ export default Route.extend(WithTokenActions, {
|
||||
});
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
return hash({
|
||||
...repo.status({
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
...this.repo.status({
|
||||
items: this.repo.findAllByDatacenter(
|
||||
this.modelFor('dc').dc.Name,
|
||||
this.modelFor('nspace').nspace.substr(1)
|
||||
),
|
||||
}),
|
||||
nspace: this.modelFor('nspace').nspace.substr(1),
|
||||
isLoading: false,
|
||||
token: this.settings.findBySlug('token'),
|
||||
});
|
||||
|
@ -1,32 +1,38 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
import WithIntentionActions from 'consul-ui/mixins/intention/with-actions';
|
||||
|
||||
// TODO: This route and the edit Route need merging somehow
|
||||
export default Route.extend(WithIntentionActions, {
|
||||
templateName: 'dc/intentions/edit',
|
||||
repo: service('repository/intention'),
|
||||
servicesRepo: service('repository/service'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
beforeModel: function() {
|
||||
this.repo.invalidate();
|
||||
},
|
||||
model: function(params) {
|
||||
this.item = this.repo.create();
|
||||
set(this.item, 'Datacenter', this.modelFor('dc').dc.Name);
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
this.item = this.repo.create({
|
||||
Datacenter: dc,
|
||||
});
|
||||
return hash({
|
||||
create: true,
|
||||
isLoading: false,
|
||||
item: this.item,
|
||||
items: this.servicesRepo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
intents: ['allow', 'deny'],
|
||||
services: this.servicesRepo.findAllByDatacenter(dc, nspace),
|
||||
nspaces: this.nspacesRepo.findAll(),
|
||||
}).then(function(model) {
|
||||
return {
|
||||
...model,
|
||||
...{
|
||||
items: [{ Name: '*' }].concat(
|
||||
model.items.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy')
|
||||
services: [{ Name: '*' }].concat(
|
||||
model.services.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy')
|
||||
),
|
||||
nspaces: [{ Name: '*' }].concat(model.nspaces.toArray()),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -3,24 +3,32 @@ import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithAclActions from 'consul-ui/mixins/intention/with-actions';
|
||||
import WithIntentionActions from 'consul-ui/mixins/intention/with-actions';
|
||||
|
||||
export default Route.extend(WithAclActions, {
|
||||
// TODO: This route and the create Route need merging somehow
|
||||
export default Route.extend(WithIntentionActions, {
|
||||
repo: service('repository/intention'),
|
||||
servicesRepo: service('repository/service'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
// We load all of your services that you are able to see here
|
||||
// as even if it doesn't exist in the namespace you are targetting
|
||||
// you may want to add it after you've added the intention
|
||||
const nspace = '*';
|
||||
return hash({
|
||||
isLoading: false,
|
||||
item: this.repo.findBySlug(params.id, this.modelFor('dc').dc.Name),
|
||||
items: this.servicesRepo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
intents: ['allow', 'deny'],
|
||||
item: this.repo.findBySlug(params.id, dc, nspace),
|
||||
services: this.servicesRepo.findAllByDatacenter(dc, nspace),
|
||||
nspaces: this.nspacesRepo.findAll(),
|
||||
}).then(function(model) {
|
||||
return {
|
||||
...model,
|
||||
...{
|
||||
items: [{ Name: '*' }].concat(
|
||||
model.items.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy')
|
||||
services: [{ Name: '*' }].concat(
|
||||
model.services.toArray().filter(item => get(item, 'Kind') !== 'connect-proxy')
|
||||
),
|
||||
nspaces: [{ Name: '*' }].concat(model.nspaces.toArray()),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -14,7 +14,10 @@ export default Route.extend(WithIntentionActions, {
|
||||
},
|
||||
model: function(params) {
|
||||
return hash({
|
||||
items: this.repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
items: this.repo.findAllByDatacenter(
|
||||
this.modelFor('dc').dc.Name,
|
||||
this.modelFor('nspace').nspace.substr(1)
|
||||
),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
import WithKvActions from 'consul-ui/mixins/kv/with-actions';
|
||||
|
||||
export default Route.extend(WithKvActions, {
|
||||
@ -12,15 +12,17 @@ export default Route.extend(WithKvActions, {
|
||||
},
|
||||
model: function(params) {
|
||||
const key = params.key || '/';
|
||||
const repo = this.repo;
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
this.item = repo.create();
|
||||
set(this.item, 'Datacenter', dc);
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
this.item = this.repo.create({
|
||||
Datacenter: dc,
|
||||
Namespace: nspace,
|
||||
});
|
||||
return hash({
|
||||
create: true,
|
||||
isLoading: false,
|
||||
item: this.item,
|
||||
parent: repo.findBySlug(key, dc),
|
||||
parent: this.repo.findBySlug(key, dc, nspace),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
|
@ -12,11 +12,11 @@ export default Route.extend(WithKvActions, {
|
||||
model: function(params) {
|
||||
const key = params.key;
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const repo = this.repo;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
return hash({
|
||||
isLoading: false,
|
||||
parent: repo.findBySlug(ascend(key, 1) || '/', dc),
|
||||
item: repo.findBySlug(key, dc),
|
||||
parent: this.repo.findBySlug(ascend(key, 1) || '/', dc, nspace),
|
||||
item: this.repo.findBySlug(key, dc, nspace),
|
||||
session: null,
|
||||
}).then(model => {
|
||||
// TODO: Consider loading this after initial page load
|
||||
@ -25,7 +25,7 @@ export default Route.extend(WithKvActions, {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
session: this.sessionRepo.findByKey(session, dc),
|
||||
session: this.sessionRepo.findByKey(session, dc, nspace),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -25,15 +25,15 @@ export default Route.extend(WithKvActions, {
|
||||
model: function(params) {
|
||||
let key = params.key || '/';
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const repo = this.repo;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
return hash({
|
||||
isLoading: false,
|
||||
parent: repo.findBySlug(key, dc),
|
||||
parent: this.repo.findBySlug(key, dc, nspace),
|
||||
}).then(model => {
|
||||
return hash({
|
||||
...model,
|
||||
...{
|
||||
items: repo.findAllBySlug(get(model.parent, 'Key'), dc).catch(e => {
|
||||
items: this.repo.findAllBySlug(get(model.parent, 'Key'), dc, nspace).catch(e => {
|
||||
const status = get(e, 'errors.firstObject.status');
|
||||
switch (status) {
|
||||
case '403':
|
||||
|
@ -13,7 +13,7 @@ export default Route.extend({
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
return hash({
|
||||
items: this.repo.findAllByDatacenter(dc),
|
||||
items: this.repo.findAllByDatacenter(dc, this.modelFor('nspace').nspace.substr(1)),
|
||||
leader: this.repo.findByLeader(dc),
|
||||
});
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';
|
||||
|
||||
@ -17,11 +16,12 @@ export default Route.extend(WithBlockingActions, {
|
||||
},
|
||||
model: function(params) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
const name = params.name;
|
||||
return hash({
|
||||
item: this.repo.findBySlug(name, dc),
|
||||
item: this.repo.findBySlug(name, dc, nspace),
|
||||
sessions: this.sessionRepo.findByNode(name, dc, nspace),
|
||||
tomography: this.coordinateRepo.findAllByNode(name, dc),
|
||||
sessions: this.sessionRepo.findByNode(name, dc),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
@ -30,12 +30,11 @@ export default Route.extend(WithBlockingActions, {
|
||||
actions: {
|
||||
invalidateSession: function(item) {
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
const controller = this.controller;
|
||||
const repo = this.sessionRepo;
|
||||
return this.feedback.execute(() => {
|
||||
const node = get(item, 'Node');
|
||||
return repo.remove(item).then(() => {
|
||||
return repo.findByNode(node, dc).then(function(sessions) {
|
||||
return this.sessionRepo.remove(item).then(() => {
|
||||
return this.sessionRepo.findByNode(item.Node, dc, nspace).then(function(sessions) {
|
||||
controller.setProperties({
|
||||
sessions: sessions,
|
||||
});
|
||||
|
14
ui-v2/app/routes/dc/nspaces/create.js
Normal file
14
ui-v2/app/routes/dc/nspaces/create.js
Normal file
@ -0,0 +1,14 @@
|
||||
import Route from './edit';
|
||||
import CreatingRoute from 'consul-ui/mixins/creating-route';
|
||||
|
||||
export default Route.extend(CreatingRoute, {
|
||||
templateName: 'dc/nspaces/edit',
|
||||
beforeModel: function() {
|
||||
// we need to skip CreatingRoute.beforeModel here
|
||||
// TODO(octane): ideally we'd like to call Route.beforeModel
|
||||
// but its not clear how to do that with old ember
|
||||
// maybe it will be more normal with modern ember
|
||||
// up until now we haven't been calling super here anyway
|
||||
// so this is probably ok until we can skip a parent super
|
||||
},
|
||||
});
|
35
ui-v2/app/routes/dc/nspaces/edit.js
Normal file
35
ui-v2/app/routes/dc/nspaces/edit.js
Normal file
@ -0,0 +1,35 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
|
||||
|
||||
export default Route.extend(WithNspaceActions, {
|
||||
repo: service('repository/nspace'),
|
||||
isCreate: function(params, transition) {
|
||||
return transition.targetName.split('.').pop() === 'create';
|
||||
},
|
||||
model: function(params, transition) {
|
||||
const repo = this.repo;
|
||||
const create = this.isCreate(...arguments);
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
return hash({
|
||||
isLoading: false,
|
||||
create: create,
|
||||
dc: dc,
|
||||
item: create
|
||||
? Promise.resolve(
|
||||
repo.create({
|
||||
ACLs: {
|
||||
PolicyDefaults: [],
|
||||
RoleDefaults: [],
|
||||
},
|
||||
})
|
||||
)
|
||||
: repo.findBySlug(params.name),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
23
ui-v2/app/routes/dc/nspaces/index.js
Normal file
23
ui-v2/app/routes/dc/nspaces/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import WithNspaceActions from 'consul-ui/mixins/nspace/with-actions';
|
||||
export default Route.extend(WithNspaceActions, {
|
||||
repo: service('repository/nspace'),
|
||||
queryParams: {
|
||||
s: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
return hash({
|
||||
items: this.repo.findAll(),
|
||||
isLoading: false,
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties(model);
|
||||
},
|
||||
});
|
@ -15,7 +15,6 @@ export default Route.extend({
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
let terms = params.s || '';
|
||||
// we check for the old style `status` variable here
|
||||
// and convert it to the new style filter=status:critical
|
||||
@ -32,7 +31,10 @@ export default Route.extend({
|
||||
}
|
||||
return hash({
|
||||
terms: terms !== '' ? terms.split('\n') : [],
|
||||
items: repo.findAllByDatacenter(this.modelFor('dc').dc.Name),
|
||||
items: this.repo.findAllByDatacenter(
|
||||
this.modelFor('dc').dc.Name,
|
||||
this.modelFor('nspace').nspace.substr(1)
|
||||
),
|
||||
});
|
||||
},
|
||||
setupController: function(controller, model) {
|
||||
|
@ -7,12 +7,11 @@ export default Route.extend({
|
||||
repo: service('repository/service'),
|
||||
proxyRepo: service('repository/proxy'),
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
const proxyRepo = this.proxyRepo;
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
return hash({
|
||||
item: repo.findInstanceBySlug(params.id, params.node, params.name, dc),
|
||||
}).then(function(model) {
|
||||
item: this.repo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace),
|
||||
}).then(model => {
|
||||
// this will not be run in a blocking loop, but this is ok as
|
||||
// its highly unlikely that a service will suddenly change to being a
|
||||
// connect-proxy or vice versa so leave as is for now
|
||||
@ -21,7 +20,7 @@ export default Route.extend({
|
||||
// proxies and mesh-gateways can't have proxies themselves so don't even look
|
||||
['connect-proxy', 'mesh-gateway'].includes(get(model.item, 'Kind'))
|
||||
? null
|
||||
: proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc),
|
||||
: this.proxyRepo.findInstanceBySlug(params.id, params.node, params.name, dc, nspace),
|
||||
...model,
|
||||
});
|
||||
});
|
||||
|
@ -12,12 +12,10 @@ export default Route.extend({
|
||||
},
|
||||
},
|
||||
model: function(params) {
|
||||
const repo = this.repo;
|
||||
const settings = this.settings;
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
return hash({
|
||||
item: repo.findBySlug(params.name, dc),
|
||||
urls: settings.findBySlug('urls'),
|
||||
item: this.repo.findBySlug(params.name, dc, this.modelFor('nspace').nspace.substr(1)),
|
||||
urls: this.settings.findBySlug('urls'),
|
||||
dc: dc,
|
||||
});
|
||||
},
|
||||
|
28
ui-v2/app/routes/nspace.js
Normal file
28
ui-v2/app/routes/nspace.js
Normal file
@ -0,0 +1,28 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default Route.extend({
|
||||
repo: service('repository/dc'),
|
||||
router: service('router'),
|
||||
model: function(params) {
|
||||
return hash({
|
||||
item: this.repo.getActive(),
|
||||
nspace: params.nspace,
|
||||
});
|
||||
},
|
||||
afterModel: function(params) {
|
||||
// We need to redirect if someone doesn't specify
|
||||
// the section they want, but not redirect if the 'section' is
|
||||
// specified (i.e. /dc-1/ vs /dc-1/services)
|
||||
// check how many parts are in the URL to figure this out
|
||||
// if there is a better way to do this then would be good to change
|
||||
if (this.router.currentURL.split('/').length < 4) {
|
||||
if (!params.nspace.startsWith('~')) {
|
||||
this.transitionTo('dc.services', params.nspace);
|
||||
} else {
|
||||
this.transitionTo('nspace.dc.services', params.nspace, params.item.Name);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
@ -7,10 +7,13 @@ export default Route.extend({
|
||||
client: service('client/http'),
|
||||
repo: service('settings'),
|
||||
dcRepo: service('repository/dc'),
|
||||
nspacesRepo: service('repository/nspace/disabled'),
|
||||
model: function(params) {
|
||||
return hash({
|
||||
item: this.repo.findAll(),
|
||||
dcs: this.dcRepo.findAll(),
|
||||
nspaces: this.nspacesRepo.findAll(),
|
||||
nspace: this.nspacesRepo.getActive(),
|
||||
}).then(model => {
|
||||
if (typeof get(model.item, 'client.blocking') === 'undefined') {
|
||||
set(model, 'item.client', { blocking: true });
|
||||
|
@ -13,15 +13,22 @@ export default Route.extend({
|
||||
typeof repo !== 'undefined'
|
||||
);
|
||||
const dc = this.modelFor('dc').dc.Name;
|
||||
const nspace = this.modelFor('nspace').nspace.substr(1);
|
||||
const create = this.isCreate(...arguments);
|
||||
return hash({
|
||||
isLoading: false,
|
||||
dc: dc,
|
||||
nspace: nspace,
|
||||
create: create,
|
||||
...repo.status({
|
||||
item: create
|
||||
? Promise.resolve(repo.create({ Datacenter: dc }))
|
||||
: repo.findBySlug(params.id, dc),
|
||||
? Promise.resolve(
|
||||
repo.create({
|
||||
Datacenter: dc,
|
||||
Namespace: nspace,
|
||||
})
|
||||
)
|
||||
: repo.findBySlug(params.id, dc, nspace),
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
20
ui-v2/app/search/filters/nspace.js
Normal file
20
ui-v2/app/search/filters/nspace.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { get } from '@ember/object';
|
||||
export default function(filterable) {
|
||||
return filterable(function(item, { s = '' }) {
|
||||
const sLower = s.toLowerCase();
|
||||
return (
|
||||
get(item, 'Name')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
get(item, 'Description')
|
||||
.toLowerCase()
|
||||
.indexOf(sLower) !== -1 ||
|
||||
(get(item, 'ACLs.PolicyDefaults') || []).some(function(item) {
|
||||
return item.Name.toLowerCase().indexOf(sLower) !== -1;
|
||||
}) ||
|
||||
(get(item, 'ACLs.RoleDefaults') || []).some(function(item) {
|
||||
return item.Name.toLowerCase().indexOf(sLower) !== -1;
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
@ -4,10 +4,15 @@ import { set } from '@ember/object';
|
||||
import {
|
||||
HEADERS_SYMBOL as HTTP_HEADERS_SYMBOL,
|
||||
HEADERS_INDEX as HTTP_HEADERS_INDEX,
|
||||
HEADERS_DATACENTER as HTTP_HEADERS_DATACENTER,
|
||||
HEADERS_NAMESPACE as HTTP_HEADERS_NAMESPACE,
|
||||
} from 'consul-ui/utils/http/consul';
|
||||
import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
import createFingerprinter from 'consul-ui/utils/create-fingerprinter';
|
||||
|
||||
const DEFAULT_NSPACE = 'default';
|
||||
|
||||
const map = function(obj, cb) {
|
||||
if (!Array.isArray(obj)) {
|
||||
return [obj].map(cb)[0];
|
||||
@ -15,26 +20,40 @@ const map = function(obj, cb) {
|
||||
return obj.map(cb);
|
||||
};
|
||||
|
||||
const attachHeaders = function(headers, body) {
|
||||
const attachHeaders = function(headers, body, query = {}) {
|
||||
// lowercase everything incase we get browser inconsistencies
|
||||
const lower = {};
|
||||
Object.keys(headers).forEach(function(key) {
|
||||
lower[key.toLowerCase()] = headers[key];
|
||||
});
|
||||
// Add a 'pretend' Datacenter/Nspace header, they are not headers
|
||||
// the come from the request but we add them here so we can use them later
|
||||
// for store reconciliation
|
||||
if (typeof query.dc !== 'undefined') {
|
||||
lower[HTTP_HEADERS_DATACENTER.toLowerCase()] = query.dc;
|
||||
}
|
||||
lower[HTTP_HEADERS_NAMESPACE.toLowerCase()] =
|
||||
typeof query.ns !== 'undefined' ? query.ns : DEFAULT_NSPACE;
|
||||
//
|
||||
body[HTTP_HEADERS_SYMBOL] = lower;
|
||||
return body;
|
||||
};
|
||||
|
||||
export default Serializer.extend({
|
||||
fingerprint: createFingerprinter(DATACENTER_KEY),
|
||||
attachHeaders: attachHeaders,
|
||||
fingerprint: createFingerprinter(DATACENTER_KEY, NSPACE_KEY, DEFAULT_NSPACE),
|
||||
respondForQuery: function(respond, query) {
|
||||
return respond((headers, body) =>
|
||||
attachHeaders(headers, map(body, this.fingerprint(this.primaryKey, this.slugKey, query.dc)))
|
||||
attachHeaders(
|
||||
headers,
|
||||
map(body, this.fingerprint(this.primaryKey, this.slugKey, query.dc)),
|
||||
query
|
||||
)
|
||||
);
|
||||
},
|
||||
respondForQueryRecord: function(respond, query) {
|
||||
return respond((headers, body) =>
|
||||
attachHeaders(headers, this.fingerprint(this.primaryKey, this.slugKey, query.dc)(body))
|
||||
attachHeaders(headers, this.fingerprint(this.primaryKey, this.slugKey, query.dc)(body), query)
|
||||
);
|
||||
},
|
||||
respondForCreateRecord: function(respond, serialized, data) {
|
||||
@ -54,6 +73,10 @@ export default Serializer.extend({
|
||||
const primaryKey = this.primaryKey;
|
||||
return respond((headers, body) => {
|
||||
// If updates are true use the info we already have
|
||||
// TODO: We may aswell avoid re-fingerprinting here if we are just
|
||||
// going to reuse data then its already fingerprinted and as the response
|
||||
// is true we don't have anything changed so the old fingerprint stays the same
|
||||
// as long as nothing in the fingerprint has been edited (the namespace?)
|
||||
if (body === true) {
|
||||
body = data;
|
||||
}
|
||||
@ -116,6 +139,8 @@ export default Serializer.extend({
|
||||
normalizeMeta: function(store, primaryModelClass, headers, payload, id, requestType) {
|
||||
const meta = {
|
||||
cursor: headers[HTTP_HEADERS_INDEX],
|
||||
dc: headers[HTTP_HEADERS_DATACENTER.toLowerCase()],
|
||||
nspace: headers[HTTP_HEADERS_NAMESPACE.toLowerCase()],
|
||||
};
|
||||
if (requestType === 'query') {
|
||||
meta.date = this.timestamp();
|
||||
|
@ -1,8 +1,7 @@
|
||||
import Serializer from './application';
|
||||
import { PRIMARY_KEY, SLUG_KEY, ATTRS } from 'consul-ui/models/intention';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/intention';
|
||||
|
||||
export default Serializer.extend({
|
||||
primaryKey: PRIMARY_KEY,
|
||||
slugKey: SLUG_KEY,
|
||||
attrs: ATTRS,
|
||||
});
|
||||
|
@ -2,6 +2,8 @@ import Serializer from './application';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/kv';
|
||||
import { NSPACE_KEY } from 'consul-ui/models/nspace';
|
||||
import { NSPACE_QUERY_PARAM as API_NSPACE_KEY } from 'consul-ui/adapters/application';
|
||||
import removeNull from 'consul-ui/utils/remove-null';
|
||||
|
||||
export default Serializer.extend({
|
||||
@ -25,6 +27,7 @@ export default Serializer.extend({
|
||||
body.map(item => {
|
||||
return {
|
||||
[this.slugKey]: item,
|
||||
[NSPACE_KEY]: query[API_NSPACE_KEY],
|
||||
};
|
||||
})
|
||||
);
|
||||
|
63
ui-v2/app/serializers/nspace.js
Normal file
63
ui-v2/app/serializers/nspace.js
Normal file
@ -0,0 +1,63 @@
|
||||
import Serializer from './application';
|
||||
import { get } from '@ember/object';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/nspace';
|
||||
|
||||
export default Serializer.extend({
|
||||
primaryKey: PRIMARY_KEY,
|
||||
slugKey: SLUG_KEY,
|
||||
respondForQuery: function(respond, serialized, data) {
|
||||
return respond((headers, body) => {
|
||||
return this.attachHeaders(
|
||||
headers,
|
||||
body.map(function(item) {
|
||||
if (get(item, 'ACLs.PolicyDefaults')) {
|
||||
item.ACLs.PolicyDefaults = item.ACLs.PolicyDefaults.map(function(item) {
|
||||
item.template = '';
|
||||
return item;
|
||||
});
|
||||
}
|
||||
// Both of these might come though unset so we make sure
|
||||
// we at least have an empty array here so we can add
|
||||
// children to them if we need to whilst saving nspaces
|
||||
['PolicyDefaults', 'RoleDefaults'].forEach(function(prop) {
|
||||
if (typeof item.ACLs === 'undefined') {
|
||||
item.ACLs = [];
|
||||
}
|
||||
if (typeof item.ACLs[prop] === 'undefined') {
|
||||
item.ACLs[prop] = [];
|
||||
}
|
||||
});
|
||||
return item;
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
respondForQueryRecord: function(respond, serialized, data) {
|
||||
// We don't attachHeaders here yet, mainly because we don't use
|
||||
// blocking queries on form views yet, and by the time we do
|
||||
// Serializers should have been refactored to not use attachHeaders
|
||||
return respond((headers, body) => {
|
||||
return body;
|
||||
});
|
||||
},
|
||||
respondForCreateRecord: function(respond, serialized, data) {
|
||||
return respond((headers, body) => {
|
||||
// The data properties sent to be saved in the backend
|
||||
// or the same ones that we receive back if its successfull
|
||||
// therefore we can just ignore the result and avoid ember-data
|
||||
// syncing problems
|
||||
return {};
|
||||
});
|
||||
},
|
||||
respondForUpdateRecord: function(respond, serialized, data) {
|
||||
return respond((headers, body) => {
|
||||
return body;
|
||||
});
|
||||
},
|
||||
respondForDeleteRecord: function(respond, serialized, data) {
|
||||
return respond((headers, body) => {
|
||||
// Deletes only need the primaryKey/uid returning
|
||||
return body;
|
||||
});
|
||||
},
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import Serializer from './application';
|
||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/service';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default Serializer.extend({
|
||||
primaryKey: PRIMARY_KEY,
|
||||
@ -8,7 +9,14 @@ export default Serializer.extend({
|
||||
// Name is added here from the query, which is used to make the uid
|
||||
// Datacenter gets added in the ApplicationSerializer
|
||||
return this._super(
|
||||
cb => respond((headers, body) => cb(headers, { Name: query.id, Nodes: body })),
|
||||
cb =>
|
||||
respond((headers, body) => {
|
||||
return cb(headers, {
|
||||
Name: query.id,
|
||||
Namespace: get(body, 'firstObject.Service.Namespace'),
|
||||
Nodes: body,
|
||||
});
|
||||
}),
|
||||
query
|
||||
);
|
||||
},
|
||||
|
@ -92,14 +92,26 @@ export default Service.extend({
|
||||
return prev;
|
||||
}, -1);
|
||||
if (doubleBreak !== -1) {
|
||||
body = values.splice(doubleBreak).reduce(function(prev, item) {
|
||||
if (typeof item !== 'string') {
|
||||
return {
|
||||
...prev,
|
||||
...item,
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
// This merges request bodies together, so you can specify multiple bodies
|
||||
// in the request and it will merge them together.
|
||||
// Turns out we never actually do this, so it might be worth removing as it complicates
|
||||
// matters slightly as we assumed post bodies would be an object.
|
||||
// This actually works as it just uses the value of the first object, if its an array
|
||||
// it concats
|
||||
body = values.splice(doubleBreak).reduce(function(prev, item, i) {
|
||||
switch (true) {
|
||||
case Array.isArray(item):
|
||||
if (i === 0) {
|
||||
prev = [];
|
||||
}
|
||||
return prev.concat(item);
|
||||
case typeof item !== 'string':
|
||||
return {
|
||||
...prev,
|
||||
...item,
|
||||
};
|
||||
default:
|
||||
return item;
|
||||
}
|
||||
}, body);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Service from '@ember/service';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { guidFor } from '@ember/object/internals';
|
||||
|
||||
// selecting
|
||||
import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
@ -8,6 +9,7 @@ import qsaFactory from 'consul-ui/utils/dom/qsa-factory';
|
||||
// see if its possible to standardize
|
||||
import sibling from 'consul-ui/utils/dom/sibling';
|
||||
import closest from 'consul-ui/utils/dom/closest';
|
||||
import isOutside from 'consul-ui/utils/dom/is-outside';
|
||||
import getComponentFactory from 'consul-ui/utils/dom/get-component-factory';
|
||||
|
||||
// events
|
||||
@ -33,10 +35,14 @@ export default Service.extend({
|
||||
viewport: function() {
|
||||
return this.win;
|
||||
},
|
||||
guid: function(el) {
|
||||
return guidFor(el);
|
||||
},
|
||||
// TODO: should this be here? Needs a better name at least
|
||||
clickFirstAnchor: clickFirstAnchor,
|
||||
closest: closest,
|
||||
sibling: sibling,
|
||||
isOutside: isOutside,
|
||||
normalizeEvent: normalizeEvent,
|
||||
listeners: createListeners,
|
||||
root: function() {
|
||||
|
@ -13,18 +13,20 @@ export default Service.extend({
|
||||
},
|
||||
//
|
||||
store: service('store'),
|
||||
findAllByDatacenter: function(dc, configuration = {}) {
|
||||
findAllByDatacenter: function(dc, nspace, configuration = {}) {
|
||||
const query = {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
query.index = configuration.cursor;
|
||||
}
|
||||
return this.store.query(this.getModelName(), query);
|
||||
},
|
||||
findBySlug: function(slug, dc, configuration = {}) {
|
||||
findBySlug: function(slug, dc, nspace, configuration = {}) {
|
||||
const query = {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
id: slug,
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
@ -44,6 +46,9 @@ export default Service.extend({
|
||||
if (typeof obj.destroyRecord === 'undefined') {
|
||||
item = obj.get('data');
|
||||
}
|
||||
// TODO: Change this to use vanilla JS
|
||||
// I think this was originally looking for a plain object
|
||||
// as opposed to an ember one
|
||||
if (typeOf(item) === 'object') {
|
||||
item = this.store.peekRecord(this.getModelName(), item[this.getPrimaryKey()]);
|
||||
}
|
||||
@ -52,6 +57,7 @@ export default Service.extend({
|
||||
});
|
||||
},
|
||||
invalidate: function() {
|
||||
// TODO: This should probably return a Promise
|
||||
this.store.unloadAll(this.getModelName());
|
||||
},
|
||||
});
|
||||
|
@ -10,8 +10,8 @@ export default RepositoryService.extend({
|
||||
getModelName: function() {
|
||||
return modelName;
|
||||
},
|
||||
findAllByNode: function(node, dc, configuration) {
|
||||
return this.findAllByDatacenter(dc, configuration).then(function(coordinates) {
|
||||
findAllByNode: function(node, dc, nspace, configuration) {
|
||||
return this.findAllByDatacenter(dc, nspace, configuration).then(function(coordinates) {
|
||||
let results = {};
|
||||
if (get(coordinates, 'length') > 1) {
|
||||
results = tomography(node, coordinates.map(item => get(item, 'data')));
|
||||
|
@ -2,6 +2,7 @@ import RepositoryService from 'consul-ui/services/repository';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import Error from '@ember/error';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
const modelName = 'dc';
|
||||
export default RepositoryService.extend({
|
||||
@ -11,6 +12,7 @@ export default RepositoryService.extend({
|
||||
},
|
||||
findAll: function() {
|
||||
return this.store.findAll(this.getModelName()).then(function(items) {
|
||||
// TODO: Move to view/template
|
||||
return items.sortBy('Name');
|
||||
});
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import { Promise } from 'rsvp';
|
||||
import isFolder from 'consul-ui/utils/isFolder';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
import { PRIMARY_KEY } from 'consul-ui/models/kv';
|
||||
|
||||
const modelName = 'kv';
|
||||
@ -13,20 +13,26 @@ export default RepositoryService.extend({
|
||||
return PRIMARY_KEY;
|
||||
},
|
||||
// this one gives you the full object so key,values and meta
|
||||
findBySlug: function(key, dc, configuration = {}) {
|
||||
findBySlug: function(key, dc, nspace, configuration = {}) {
|
||||
if (isFolder(key)) {
|
||||
const id = JSON.stringify([dc, key]);
|
||||
// TODO: This very much shouldn't be here,
|
||||
// needs to eventually use ember-datas generateId thing
|
||||
// in the meantime at least our fingerprinter
|
||||
const id = JSON.stringify([nspace, dc, key]);
|
||||
let item = this.store.peekRecord(this.getModelName(), id);
|
||||
if (!item) {
|
||||
item = this.create();
|
||||
set(item, 'Key', key);
|
||||
set(item, 'Datacenter', dc);
|
||||
item = this.create({
|
||||
Key: key,
|
||||
Datacenter: dc,
|
||||
Namespace: nspace,
|
||||
});
|
||||
}
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
const query = {
|
||||
id: key,
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
query.index = configuration.cursor;
|
||||
@ -35,13 +41,14 @@ export default RepositoryService.extend({
|
||||
},
|
||||
// this one only gives you keys
|
||||
// https://www.consul.io/api/kv.html
|
||||
findAllBySlug: function(key, dc, configuration = {}) {
|
||||
findAllBySlug: function(key, dc, nspace, configuration = {}) {
|
||||
if (key === '/') {
|
||||
key = '';
|
||||
}
|
||||
const query = {
|
||||
id: key,
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
separator: '/',
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
@ -55,7 +62,13 @@ export default RepositoryService.extend({
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
if (e.errors && e.errors[0] && e.errors[0].status == '404') {
|
||||
// TODO: Double check this was loose on purpose, its probably as we were unsure of
|
||||
// type of ember-data error.Status at first, we could probably change this
|
||||
// to `===` now
|
||||
if (get(e, 'errors.firstObject.status') == '404') {
|
||||
// TODO: This very much shouldn't be here,
|
||||
// needs to eventually use ember-datas generateId thing
|
||||
// in the meantime at least our fingerprinter
|
||||
const id = JSON.stringify([dc, key]);
|
||||
const record = this.store.peekRecord(this.getModelName(), id);
|
||||
if (record) {
|
||||
|
15
ui-v2/app/services/repository/nspace.js
Normal file
15
ui-v2/app/services/repository/nspace.js
Normal file
@ -0,0 +1,15 @@
|
||||
import RepositoryService from 'consul-ui/services/repository';
|
||||
|
||||
const modelName = 'nspace';
|
||||
export default RepositoryService.extend({
|
||||
getModelName: function() {
|
||||
return modelName;
|
||||
},
|
||||
findAll: function(configuration = {}) {
|
||||
const query = {};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
query.index = configuration.cursor;
|
||||
}
|
||||
return this.store.query(this.getModelName(), query);
|
||||
},
|
||||
});
|
21
ui-v2/app/services/repository/nspace/disabled.js
Normal file
21
ui-v2/app/services/repository/nspace/disabled.js
Normal file
@ -0,0 +1,21 @@
|
||||
import RepositoryService from 'consul-ui/services/repository';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
const modelName = 'nspace';
|
||||
const DEFAULT_NSPACE = 'default';
|
||||
export default RepositoryService.extend({
|
||||
getModelName: function() {
|
||||
return modelName;
|
||||
},
|
||||
findAll: function(configuration = {}) {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
getActive: function() {
|
||||
return {
|
||||
Name: DEFAULT_NSPACE,
|
||||
};
|
||||
},
|
||||
authorize: function(dc, nspace) {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
});
|
73
ui-v2/app/services/repository/nspace/enabled.js
Normal file
73
ui-v2/app/services/repository/nspace/enabled.js
Normal file
@ -0,0 +1,73 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import { get } from '@ember/object';
|
||||
import { config } from 'consul-ui/env';
|
||||
import RepositoryService from 'consul-ui/services/repository';
|
||||
|
||||
const modelName = 'nspace';
|
||||
export default RepositoryService.extend({
|
||||
router: service('router'),
|
||||
settings: service('settings'),
|
||||
getModelName: function() {
|
||||
return modelName;
|
||||
},
|
||||
findAll: function(configuration = {}) {
|
||||
const query = {};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
query.index = configuration.cursor;
|
||||
}
|
||||
return this.store.query(this.getModelName(), query);
|
||||
},
|
||||
authorize: function(dc, nspace) {
|
||||
if (!config('CONSUL_ACLS_ENABLED')) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
Resource: 'operator',
|
||||
Access: 'write',
|
||||
Allow: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return this.store.authorize(this.getModelName(), { dc: dc, ns: nspace }).catch(function(e) {
|
||||
return [];
|
||||
});
|
||||
},
|
||||
getActive: function() {
|
||||
let routeParams = {};
|
||||
// this is only populated before the model hook as fired,
|
||||
// it is then deleted after the model hook has finished
|
||||
const infos = get(this, 'router._router.currentState.router.activeTransition.routeInfos');
|
||||
if (typeof infos !== 'undefined') {
|
||||
infos.forEach(function(item) {
|
||||
Object.keys(item.params).forEach(function(prop) {
|
||||
routeParams[prop] = item.params[prop];
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// this is only populated after the model hook has finished
|
||||
//
|
||||
const current = get(this, 'router.currentRoute');
|
||||
if (current) {
|
||||
const nspacedRoute = current.find(function(item, i, arr) {
|
||||
return item.paramNames.includes('nspace');
|
||||
});
|
||||
if (typeof nspacedRoute !== 'undefined') {
|
||||
routeParams.nspace = nspacedRoute.params.nspace;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.settings
|
||||
.findBySlug('nspace')
|
||||
.then(function(nspace) {
|
||||
// If we can't figure out the nspace from the URL use
|
||||
// the previously saved nspace and if thats not there
|
||||
// then just use default
|
||||
return routeParams.nspace || nspace || '~default';
|
||||
})
|
||||
.then(nspace => this.settings.persist({ nspace: nspace }))
|
||||
.then(function(item) {
|
||||
return {
|
||||
Name: item.nspace.substr(1),
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
@ -9,9 +9,10 @@ export default RepositoryService.extend({
|
||||
getPrimaryKey: function() {
|
||||
return PRIMARY_KEY;
|
||||
},
|
||||
findAllBySlug: function(slug, dc, configuration = {}) {
|
||||
findAllBySlug: function(slug, dc, nspace, configuration = {}) {
|
||||
const query = {
|
||||
id: slug,
|
||||
ns: nspace,
|
||||
dc: dc,
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
@ -19,8 +20,8 @@ export default RepositoryService.extend({
|
||||
}
|
||||
return this.store.query(this.getModelName(), query);
|
||||
},
|
||||
findInstanceBySlug: function(id, node, slug, dc, configuration) {
|
||||
return this.findAllBySlug(slug, dc, configuration).then(function(items) {
|
||||
findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) {
|
||||
return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) {
|
||||
let res = {};
|
||||
if (get(items, 'length') > 0) {
|
||||
let instance = items.filterBy('ServiceProxy.DestinationServiceID', id).findBy('Node', node);
|
||||
|
@ -7,6 +7,7 @@ export default RepositoryService.extend({
|
||||
},
|
||||
findBySlug: function(slug, dc) {
|
||||
return this._super(...arguments).then(function(item) {
|
||||
// TODO: Move this to the Serializer
|
||||
const nodes = get(item, 'Nodes');
|
||||
if (nodes.length === 0) {
|
||||
// TODO: Add an store.error("404", "message") or similar
|
||||
@ -21,6 +22,7 @@ export default RepositoryService.extend({
|
||||
throw e;
|
||||
}
|
||||
const service = get(nodes, 'firstObject');
|
||||
// TODO: Use [...new Set()] instead of uniq
|
||||
const tags = nodes
|
||||
.reduce(function(prev, item) {
|
||||
return prev.concat(get(item, 'Service.Tags') || []);
|
||||
@ -29,11 +31,13 @@ export default RepositoryService.extend({
|
||||
set(service, 'Tags', tags);
|
||||
set(service, 'Nodes', nodes);
|
||||
set(service, 'meta', get(item, 'meta'));
|
||||
set(service, 'Namespace', get(item, 'Namespace'));
|
||||
return service;
|
||||
});
|
||||
},
|
||||
findInstanceBySlug: function(id, node, slug, dc, configuration) {
|
||||
return this.findBySlug(slug, dc, configuration).then(function(item) {
|
||||
// TODO: Move this to the Serializer
|
||||
// Loop through all the service instances and pick out the one
|
||||
// that has the same service id AND node name
|
||||
// node names are unique per datacenter
|
||||
@ -50,6 +54,7 @@ export default RepositoryService.extend({
|
||||
return item.ServiceID == '';
|
||||
});
|
||||
set(service, 'meta', get(item, 'meta'));
|
||||
set(service, 'Namespace', get(item, 'Namespace'));
|
||||
return service;
|
||||
}
|
||||
// TODO: Add an store.error("404", "message") or similar
|
||||
|
@ -7,10 +7,11 @@ export default RepositoryService.extend({
|
||||
getModelName: function() {
|
||||
return modelName;
|
||||
},
|
||||
findByNode: function(node, dc, configuration = {}) {
|
||||
findByNode: function(node, dc, nspace, configuration = {}) {
|
||||
const query = {
|
||||
id: node,
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
};
|
||||
if (typeof configuration.cursor !== 'undefined') {
|
||||
query.index = configuration.cursor;
|
||||
@ -18,7 +19,7 @@ export default RepositoryService.extend({
|
||||
return this.store.query(this.getModelName(), query);
|
||||
},
|
||||
// TODO: Why Key? Probably should be findBySlug like the others
|
||||
findByKey: function(slug, dc) {
|
||||
return this.findBySlug(slug, dc);
|
||||
findByKey: function(slug, dc, nspace) {
|
||||
return this.findBySlug(...arguments);
|
||||
},
|
||||
});
|
||||
|
@ -43,16 +43,18 @@ export default RepositoryService.extend({
|
||||
clone: function(item) {
|
||||
return this.store.clone(this.getModelName(), get(item, PRIMARY_KEY));
|
||||
},
|
||||
findByPolicy: function(id, dc) {
|
||||
findByPolicy: function(id, dc, nspace) {
|
||||
return this.store.query(this.getModelName(), {
|
||||
policy: id,
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
},
|
||||
findByRole: function(id, dc) {
|
||||
findByRole: function(id, dc, nspace) {
|
||||
return this.store.query(this.getModelName(), {
|
||||
role: id,
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -17,10 +17,20 @@ const createProxy = function(repo, find, settings, cache, serialize = JSON.strin
|
||||
const meta = get(event.data || {}, 'meta') || {};
|
||||
if (typeof meta.date !== 'undefined') {
|
||||
// unload anything older than our current sync date/time
|
||||
const checkNspace = meta.nspace !== '';
|
||||
store.peekAll(repo.getModelName()).forEach(function(item) {
|
||||
const date = get(item, 'SyncTime');
|
||||
if (typeof date !== 'undefined' && date != meta.date) {
|
||||
store.unloadRecord(item);
|
||||
const dc = get(item, 'Datacenter');
|
||||
if (dc === meta.dc) {
|
||||
if (checkNspace) {
|
||||
const nspace = get(item, 'Namespace');
|
||||
if (nspace !== meta.namespace) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const date = get(item, 'SyncTime');
|
||||
if (typeof date !== 'undefined' && date != meta.date) {
|
||||
store.unloadRecord(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -31,4 +31,10 @@ export default Store.extend({
|
||||
const adapter = this.adapterFor(modelName);
|
||||
return adapter.queryLeader(this, { modelName: modelName }, null, query);
|
||||
},
|
||||
// TODO: This one is only for ACL, should fail nicely if you call it
|
||||
// for anything other than ACLs for good DX
|
||||
authorize: function(modelName, query = {}) {
|
||||
// TODO: no normalization, type it properly for the moment
|
||||
return this.adapterFor(modelName).authorize(this, { modelName: modelName }, null, query);
|
||||
},
|
||||
});
|
||||
|
@ -35,7 +35,7 @@
|
||||
padding-bottom: calc(0.4em - 1px) !important;
|
||||
}
|
||||
%internal-button {
|
||||
padding: 0.75rem 1rem;
|
||||
padding: 0.9em 1em;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
|
@ -95,6 +95,9 @@
|
||||
@extend %frame-red-900;
|
||||
}
|
||||
|
||||
%internal-button {
|
||||
color: $gray-900;
|
||||
}
|
||||
%internal-button-dangerous {
|
||||
@extend %frame-red-300;
|
||||
}
|
||||
@ -102,7 +105,7 @@
|
||||
@extend %frame-red-700;
|
||||
}
|
||||
%internal-button-intent {
|
||||
background-color: $gray-100;
|
||||
background-color: $gray-050;
|
||||
}
|
||||
%internal-button:focus,
|
||||
%internal-button:hover {
|
||||
|
@ -9,6 +9,9 @@
|
||||
%stats-card li {
|
||||
border-color: $gray-200;
|
||||
}
|
||||
%stats-card a {
|
||||
color: $gray-900;
|
||||
}
|
||||
%stats-card,
|
||||
%stats-card header::before {
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
|
||||
|
@ -26,6 +26,9 @@
|
||||
%table td a {
|
||||
display: block;
|
||||
}
|
||||
%table td.no-actions ~ .actions {
|
||||
display: none;
|
||||
}
|
||||
%table td:not(.actions),
|
||||
%table td:not(.actions) > *:only-child {
|
||||
overflow-x: hidden;
|
||||
@ -50,3 +53,11 @@
|
||||
%table td a {
|
||||
padding-right: 0.9em;
|
||||
}
|
||||
%table tbody td em {
|
||||
display: block;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
%table tbody td em {
|
||||
color: $gray-500;
|
||||
}
|
||||
|
@ -15,7 +15,9 @@
|
||||
}
|
||||
/* TODO: Add to native selector `tbody th` - will involve moving all
|
||||
* current th's to `thead th` and changing the templates
|
||||
* at whichpoint we can probably remove the %table a selector from here
|
||||
*/
|
||||
%table a,
|
||||
%tbody-th {
|
||||
color: $gray-900;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user