ui: Add license endpoint/datasource (#12506)
* ui: Add auto-pilot/state endpoint usage (merged into DC models) (#12514) * ui: Catalog Health Overview DataSource (#12520)
This commit is contained in:
parent
d8fca60bd8
commit
7c4dd75abd
|
@ -64,6 +64,12 @@ module.exports = {
|
||||||
urlSchema: 'auto',
|
urlSchema: 'auto',
|
||||||
urlPrefix: 'docs/styles',
|
urlPrefix: 'docs/styles',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
root: path.resolve(__dirname, 'app/services/repository'),
|
||||||
|
pattern: '**/*.mdx',
|
||||||
|
urlSchema: 'auto',
|
||||||
|
urlPrefix: 'docs/repositories',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
root: path.resolve(__dirname, 'app/modifiers'),
|
root: path.resolve(__dirname, 'app/modifiers'),
|
||||||
pattern: '**/*.mdx',
|
pattern: '**/*.mdx',
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import Adapter from './application';
|
|
||||||
|
|
||||||
export default class DcAdapter extends Adapter {
|
|
||||||
requestForQuery(request) {
|
|
||||||
return request`
|
|
||||||
GET /v1/catalog/datacenters
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,10 +7,10 @@ export default path => (target, propertyKey, desc) => {
|
||||||
runInDebug(() => {
|
runInDebug(() => {
|
||||||
routes[path] = { cls: target, method: propertyKey };
|
routes[path] = { cls: target, method: propertyKey };
|
||||||
});
|
});
|
||||||
router.on(path, function(params, owner) {
|
router.on(path, function(params, owner, request) {
|
||||||
const container = owner.lookup('service:container');
|
const container = owner.lookup('service:container');
|
||||||
const instance = container.get(target);
|
const instance = container.get(target);
|
||||||
return configuration => desc.value.apply(instance, [params, configuration]);
|
return configuration => desc.value.apply(instance, [params, configuration, request]);
|
||||||
});
|
});
|
||||||
return desc;
|
return desc;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
|
export default helper(function(args, hash) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(...args);
|
||||||
|
} catch(e) {
|
||||||
|
return args[0].map(item => JSON.stringify(item, args[1], args[2]));
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,8 +5,16 @@ export const FOREIGN_KEY = 'Datacenter';
|
||||||
export const SLUG_KEY = 'Name';
|
export const SLUG_KEY = 'Name';
|
||||||
|
|
||||||
export default class Datacenter extends Model {
|
export default class Datacenter extends Model {
|
||||||
@attr('string') uid;
|
@attr('string') uri;
|
||||||
@attr('string') Name;
|
@attr('string') Name;
|
||||||
|
// autopilot/state
|
||||||
|
@attr('boolean') Healthy;
|
||||||
|
@attr('number') FailureTolerance;
|
||||||
|
@attr('number') OptimisticFailureTolerance;
|
||||||
|
@attr('string') Leader;
|
||||||
|
@attr() Voters; // []
|
||||||
|
@attr() Servers; // [] the API uses {} but we reshape that on the frontend
|
||||||
|
//
|
||||||
@attr('boolean') Local;
|
@attr('boolean') Local;
|
||||||
@attr('boolean') Primary;
|
@attr('boolean') Primary;
|
||||||
@attr('string') DefaultACLPolicy;
|
@attr('string') DefaultACLPolicy;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import Model, { attr } from '@ember-data/model';
|
||||||
|
|
||||||
|
export const PRIMARY_KEY = 'uri';
|
||||||
|
|
||||||
|
export default class License extends Model {
|
||||||
|
@attr('string') uri;
|
||||||
|
@attr('boolean') Valid;
|
||||||
|
|
||||||
|
@attr('number') SyncTime;
|
||||||
|
@attr() meta; // {}
|
||||||
|
|
||||||
|
@attr('string') Datacenter;
|
||||||
|
@attr('string') Namespace;
|
||||||
|
@attr('string') Partition;
|
||||||
|
|
||||||
|
@attr() License; // {}
|
||||||
|
// @attr() Warnings; // []
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
import Serializer from './application';
|
|
||||||
import { inject as service } from '@ember/service';
|
|
||||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/dc';
|
|
||||||
|
|
||||||
import {
|
|
||||||
HEADERS_SYMBOL,
|
|
||||||
HEADERS_DEFAULT_ACL_POLICY as DEFAULT_ACL_POLICY,
|
|
||||||
} from 'consul-ui/utils/http/consul';
|
|
||||||
export default class DcSerializer extends Serializer {
|
|
||||||
@service('env') env;
|
|
||||||
|
|
||||||
primaryKey = PRIMARY_KEY;
|
|
||||||
slugKey = SLUG_KEY;
|
|
||||||
|
|
||||||
// datacenters come in as an array of plain strings. Convert to objects
|
|
||||||
// instead and collect all the other datacenter info from other places and
|
|
||||||
// add it to each datacenter object
|
|
||||||
respondForQuery(respond, query) {
|
|
||||||
return super.respondForQuery(
|
|
||||||
cb => respond((headers, body) => {
|
|
||||||
body = body.map(item => ({
|
|
||||||
Datacenter: '',
|
|
||||||
[this.slugKey]: item,
|
|
||||||
}));
|
|
||||||
body = cb(headers, body);
|
|
||||||
headers = body[HEADERS_SYMBOL];
|
|
||||||
|
|
||||||
const Local = this.env.var('CONSUL_DATACENTER_LOCAL');
|
|
||||||
const Primary = this.env.var('CONSUL_DATACENTER_PRIMARY');
|
|
||||||
const DefaultACLPolicy = headers[DEFAULT_ACL_POLICY.toLowerCase()];
|
|
||||||
|
|
||||||
return body.map(item => ({
|
|
||||||
...item,
|
|
||||||
Local: item.Name === Local,
|
|
||||||
Primary: item.Name === Primary,
|
|
||||||
DefaultACLPolicy: DefaultACLPolicy,
|
|
||||||
}));
|
|
||||||
}),
|
|
||||||
query
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -83,11 +83,31 @@ export default class HttpService extends Service {
|
||||||
@service('client/connections') connections;
|
@service('client/connections') connections;
|
||||||
@service('client/transports/xhr') transport;
|
@service('client/transports/xhr') transport;
|
||||||
@service('settings') settings;
|
@service('settings') settings;
|
||||||
|
@service('encoder') encoder;
|
||||||
|
@service('store') store;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(...arguments);
|
super.init(...arguments);
|
||||||
this._listeners = this.dom.listeners();
|
this._listeners = this.dom.listeners();
|
||||||
this.parseURL = createURL(encodeURIComponent, obj => QueryParams.stringify(this.sanitize(obj)));
|
this.parseURL = createURL(encodeURIComponent, obj => QueryParams.stringify(this.sanitize(obj)));
|
||||||
|
const uriTag = this.encoder.uriTag();
|
||||||
|
this.cache = (data, id) => {
|
||||||
|
// interpolate the URI
|
||||||
|
data.uri = id(uriTag);
|
||||||
|
// save the time we received it for cache management purposes
|
||||||
|
data.SyncTime = new Date().getTime();
|
||||||
|
// save the data to the cache
|
||||||
|
return this.store.push(
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
id: data.uri,
|
||||||
|
// the model is encoded as the protocol in the URI
|
||||||
|
type: new URL(data.uri).protocol.slice(0, -1),
|
||||||
|
attributes: data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitize(obj) {
|
sanitize(obj) {
|
||||||
|
@ -197,9 +217,9 @@ export default class HttpService extends Service {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
request(cb) {
|
request(cb) {
|
||||||
const client = this;
|
const client = this;
|
||||||
|
const cache = this.cache;
|
||||||
return cb(function(strs, ...values) {
|
return cb(function(strs, ...values) {
|
||||||
const params = client.requestParams(...arguments);
|
const params = client.requestParams(...arguments);
|
||||||
return client.settings.findBySlug('token').then(token => {
|
return client.settings.findBySlug('token').then(token => {
|
||||||
|
@ -236,7 +256,28 @@ export default class HttpService extends Service {
|
||||||
[CONSUL_PARTITION]: params.data.partition || token.Partition || 'default',
|
[CONSUL_PARTITION]: params.data.partition || token.Partition || 'default',
|
||||||
};
|
};
|
||||||
const respond = function(cb) {
|
const respond = function(cb) {
|
||||||
return cb(headers, e.data.response);
|
let res = cb(headers, e.data.response, cache);
|
||||||
|
const meta = res.meta || {};
|
||||||
|
if(meta.version === 2) {
|
||||||
|
if(Array.isArray(res.body)) {
|
||||||
|
res = new Proxy(
|
||||||
|
res.body,
|
||||||
|
{
|
||||||
|
get: (target, prop) => {
|
||||||
|
switch(prop) {
|
||||||
|
case 'meta':
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
return target[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
res = res.body;
|
||||||
|
res.meta = meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
next(() => resolve(respond));
|
next(() => resolve(respond));
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,11 +3,17 @@ import { getOwner } from '@ember/application';
|
||||||
import { match } from 'consul-ui/decorators/data-source';
|
import { match } from 'consul-ui/decorators/data-source';
|
||||||
|
|
||||||
export default class HttpService extends Service {
|
export default class HttpService extends Service {
|
||||||
|
@service('client/http') client;
|
||||||
@service('data-source/protocols/http/blocking') type;
|
@service('data-source/protocols/http/blocking') type;
|
||||||
|
|
||||||
source(src, configuration) {
|
source(src, configuration) {
|
||||||
const route = match(src);
|
const route = match(src);
|
||||||
const find = route.cb(route.params, getOwner(this));
|
let find;
|
||||||
|
this.client.request(
|
||||||
|
request => {
|
||||||
|
find = route.cb(route.params, getOwner(this), request);
|
||||||
|
}
|
||||||
|
);
|
||||||
return this.type.source(find, configuration);
|
return this.type.source(find, configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,180 @@
|
||||||
import Error from '@ember/error';
|
import Error from '@ember/error';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
import RepositoryService from 'consul-ui/services/repository';
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
import dataSource from 'consul-ui/decorators/data-source';
|
import dataSource from 'consul-ui/decorators/data-source';
|
||||||
|
import {
|
||||||
|
HEADERS_DEFAULT_ACL_POLICY as DEFAULT_ACL_POLICY,
|
||||||
|
} from 'consul-ui/utils/http/consul';
|
||||||
|
|
||||||
|
|
||||||
|
const SECONDS = 1000;
|
||||||
|
const MODEL_NAME = 'dc';
|
||||||
|
|
||||||
|
const zero = {
|
||||||
|
Total: 0,
|
||||||
|
Passing: 0,
|
||||||
|
Warning: 0,
|
||||||
|
Critical: 0
|
||||||
|
};
|
||||||
|
const aggregate = (prev, body, type) => {
|
||||||
|
|
||||||
|
return body[type].reduce((prev, item) => {
|
||||||
|
|
||||||
|
// for each Partitions, Namespaces
|
||||||
|
['Partition', 'Namespace'].forEach(bucket => {
|
||||||
|
|
||||||
|
// lazily initialize
|
||||||
|
let obj = prev[bucket][item[bucket]];
|
||||||
|
if(typeof obj === 'undefined') {
|
||||||
|
obj = prev[bucket][item[bucket]] = {
|
||||||
|
Name: item[bucket],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if(typeof obj[type] === 'undefined') {
|
||||||
|
obj[type] = {
|
||||||
|
...zero
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//
|
||||||
|
|
||||||
|
// accumulate
|
||||||
|
obj[type].Total += item.Total;
|
||||||
|
obj[type].Passing += item.Passing;
|
||||||
|
obj[type].Warning += item.Warning;
|
||||||
|
obj[type].Critical += item.Critical;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// also aggregate the Datacenter, without doubling up
|
||||||
|
// for Partitions/Namespaces
|
||||||
|
prev.Datacenter[type].Total += item.Total;
|
||||||
|
prev.Datacenter[type].Passing += item.Passing;
|
||||||
|
prev.Datacenter[type].Warning += item.Warning;
|
||||||
|
prev.Datacenter[type].Critical += item.Critical;
|
||||||
|
return prev;
|
||||||
|
}, prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const modelName = 'dc';
|
|
||||||
export default class DcService extends RepositoryService {
|
export default class DcService extends RepositoryService {
|
||||||
|
@service('env') env;
|
||||||
|
|
||||||
getModelName() {
|
getModelName() {
|
||||||
return modelName;
|
return MODEL_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@dataSource('/:partition/:ns/:dc/datacenters')
|
@dataSource('/:partition/:ns/:dc/datacenters')
|
||||||
async findAll() {
|
async fetchAll({partition, ns, dc}, { uri }, request) {
|
||||||
return super.findAll(...arguments);
|
const Local = this.env.var('CONSUL_DATACENTER_LOCAL');
|
||||||
|
const Primary = this.env.var('CONSUL_DATACENTER_PRIMARY');
|
||||||
|
return (await request`
|
||||||
|
GET /v1/catalog/datacenters
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
`)(
|
||||||
|
(headers, body, cache) => {
|
||||||
|
// TODO: Not sure nowadays whether we need to keep lowercasing everything
|
||||||
|
// I vaguely remember when I last looked it was not needed for browsers anymore
|
||||||
|
// but I also vaguely remember something about Pretender lowercasing things still
|
||||||
|
// so if we can work around Pretender I think we can remove all the header lowercasing
|
||||||
|
// For the moment we lowercase here so as to not effect the ember-data-flavoured-v1 fork
|
||||||
|
const entry = Object.entries(headers)
|
||||||
|
.find(([key, value]) => key.toLowerCase() === DEFAULT_ACL_POLICY.toLowerCase());
|
||||||
|
//
|
||||||
|
const DefaultACLPolicy = entry[1] || 'allow';
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
version: 2,
|
||||||
|
uri: uri,
|
||||||
|
},
|
||||||
|
body: body.map(dc => {
|
||||||
|
return cache(
|
||||||
|
{
|
||||||
|
Name: dc,
|
||||||
|
Datacenter: '',
|
||||||
|
Local: dc === Local,
|
||||||
|
Primary: dc === Primary,
|
||||||
|
DefaultACLPolicy: DefaultACLPolicy,
|
||||||
|
},
|
||||||
|
uri => uri`${MODEL_NAME}:///${''}/${''}/${dc}/datacenter`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@dataSource('/:partition/:ns/:dc/datacenter/:name')
|
@dataSource('/:partition/:ns/:dc/datacenter')
|
||||||
async findBySlug(params) {
|
async fetch({partition, ns, dc}, { uri }, request) {
|
||||||
|
return (await request`
|
||||||
|
GET /v1/operator/autopilot/state?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
`)(
|
||||||
|
(headers, body, cache) => ({
|
||||||
|
meta: {
|
||||||
|
version: 2,
|
||||||
|
uri: uri,
|
||||||
|
interval: 30 * SECONDS
|
||||||
|
},
|
||||||
|
body: cache(
|
||||||
|
{
|
||||||
|
...body,
|
||||||
|
// turn servers into an array instead of a map/object
|
||||||
|
Servers: Object.values(body.Servers)
|
||||||
|
},
|
||||||
|
uri => uri`${MODEL_NAME}:///${''}/${''}/${dc}/datacenter`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@dataSource('/:partition/:ns/:dc/catalog/health')
|
||||||
|
async fetchCatalogHealth({partition, ns, dc}, { uri }, request) {
|
||||||
|
return (await request`
|
||||||
|
GET /v1/internal/ui/catalog-overview?${{ dc, stale: null }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
`)(
|
||||||
|
(headers, body, cache) => {
|
||||||
|
|
||||||
|
|
||||||
|
// for each Services/Nodes/Checks aggregate
|
||||||
|
const agg = ['Nodes', 'Services', 'Checks']
|
||||||
|
.reduce((prev, item) => aggregate(prev, body, item), {
|
||||||
|
Datacenter: {
|
||||||
|
Name: dc,
|
||||||
|
Nodes: {
|
||||||
|
...zero
|
||||||
|
},
|
||||||
|
Services: {
|
||||||
|
...zero
|
||||||
|
},
|
||||||
|
Checks: {
|
||||||
|
...zero
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Partition: {},
|
||||||
|
Namespace: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
version: 2,
|
||||||
|
uri: uri,
|
||||||
|
interval: 30 * SECONDS
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
Datacenter: agg.Datacenter,
|
||||||
|
Partitions: Object.values(agg.Partition),
|
||||||
|
Namespaces: Object.values(agg.Namespace),
|
||||||
|
...body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@dataSource('/:partition/:ns/:dc/datacenter-cache/:name')
|
||||||
|
async find(params) {
|
||||||
const items = this.store.peekAll('dc');
|
const items = this.store.peekAll('dc');
|
||||||
const item = items.findBy('Name', params.name);
|
const item = items.findBy('Name', params.name);
|
||||||
if (typeof item === 'undefined') {
|
if (typeof item === 'undefined') {
|
||||||
|
@ -26,4 +186,5 @@ export default class DcService extends RepositoryService {
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Dc
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>URI: <code>/:partition/:nspace/:dc/catalog/health</code></figcaption>
|
||||||
|
<DataSource
|
||||||
|
@src={{
|
||||||
|
uri '/${partition}/${nspace}/${dc}/catalog/health'
|
||||||
|
(hash
|
||||||
|
partition='partition'
|
||||||
|
nspace='ns'
|
||||||
|
dc='dc1'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@onchange={{action (mut data) value="data"}}
|
||||||
|
/>
|
||||||
|
<pre><code>{{json-stringify data null 4}}</code></pre>
|
||||||
|
</figure>
|
||||||
|
```
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>URI: <code>/:partition/:nspace/:dc/datacenters</code></figcaption>
|
||||||
|
<DataSource
|
||||||
|
@src={{
|
||||||
|
uri '/${partition}/${nspace}/${dc}/datacenters'
|
||||||
|
(hash
|
||||||
|
partition='partition'
|
||||||
|
nspace='ns'
|
||||||
|
dc='dc1'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@onchange={{action (mut data) value="data"}}
|
||||||
|
/>
|
||||||
|
<pre><code>{{json-stringify data null 4}}</code></pre>
|
||||||
|
</figure>
|
||||||
|
```
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>URI: <code>/:partition/:nspace/:dc/datacenter</code></figcaption>
|
||||||
|
<DataSource
|
||||||
|
@src={{
|
||||||
|
uri '/${partition}/${nspace}/${dc}/datacenter'
|
||||||
|
(hash
|
||||||
|
partition='partition'
|
||||||
|
nspace='ns'
|
||||||
|
dc='dc1'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@onchange={{action (mut data) value="data"}}
|
||||||
|
/>
|
||||||
|
<pre><code>{{json-stringify data null 4}}</code></pre>
|
||||||
|
</figure>
|
||||||
|
```
|
|
@ -0,0 +1,37 @@
|
||||||
|
import RepositoryService from 'consul-ui/services/repository';
|
||||||
|
import dataSource from 'consul-ui/decorators/data-source';
|
||||||
|
|
||||||
|
const MODEL_NAME = 'license';
|
||||||
|
|
||||||
|
const bucket = function(item, { dc, ns = 'default', partition = 'default' }) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
Datacenter: dc,
|
||||||
|
Namespace: typeof item.Namespace === 'undefined' ? ns : item.Namespace,
|
||||||
|
Partition: typeof item.Partition === 'undefined' ? partition : item.Partition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const SECONDS = 1000;
|
||||||
|
|
||||||
|
export default class LicenseService extends RepositoryService {
|
||||||
|
@dataSource('/:partition/:ns/:dc/license')
|
||||||
|
async find({partition, ns, dc}, { uri }, request) {
|
||||||
|
return (await request`
|
||||||
|
GET /v1/operator/license?${{ dc }}
|
||||||
|
X-Request-ID: ${uri}
|
||||||
|
`)(
|
||||||
|
(headers, body, cache) => ({
|
||||||
|
meta: {
|
||||||
|
version: 2,
|
||||||
|
uri: uri,
|
||||||
|
interval: 30 * SECONDS
|
||||||
|
},
|
||||||
|
body: cache(
|
||||||
|
bucket(body, { dc }),
|
||||||
|
uri => uri`${MODEL_NAME}:///${partition}/${ns}/${dc}/license/${body.License.license_id}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
# License
|
||||||
|
|
||||||
|
```hbs preview-template
|
||||||
|
<figure>
|
||||||
|
<figcaption>URI: <code>/:partition/:nspace/:dc/license</code></figcaption>
|
||||||
|
<DataSource
|
||||||
|
@src={{
|
||||||
|
uri '/${partition}/${nspace}/${dc}/license'
|
||||||
|
(hash
|
||||||
|
partition='partition'
|
||||||
|
nspace='ns'
|
||||||
|
dc='dc-1'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@onchange={{action (mut data) value="data"}}
|
||||||
|
/>
|
||||||
|
<pre><code>{{json-stringify data null 4}}</code></pre>
|
||||||
|
</figure>
|
||||||
|
```
|
|
@ -113,7 +113,7 @@ as |dc dcs|}}
|
||||||
|
|
||||||
{{! figure out our current DC and convert it to a model }}
|
{{! figure out our current DC and convert it to a model }}
|
||||||
<DataSource
|
<DataSource
|
||||||
@src={{uri '/${partition}/*/${dc}/datacenter/${name}'
|
@src={{uri '/${partition}/*/${dc}/datacenter-cache/${name}'
|
||||||
(hash
|
(hash
|
||||||
dc=dc.Name
|
dc=dc.Name
|
||||||
partition=partition
|
partition=partition
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
${[0].map(_ => {
|
||||||
|
|
||||||
|
const healthOf = num => {
|
||||||
|
return range(num).reduce((prev, _) => {
|
||||||
|
prev[fake.helpers.randomize(['Passing', 'Warning', 'Critical'])] ++;
|
||||||
|
return prev;
|
||||||
|
}, {Passing: 0, Warning: 0, Critical: 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
const partitionCount = env('CONSUL_PARTITION_COUNT', Math.floor(Math.random() * 5));
|
||||||
|
const nspaceCount = env('CONSUL_NSPACE_COUNT', Math.floor(Math.random() * 10));
|
||||||
|
const nodes = env('CONSUL_NODE_COUNT', Math.floor(Math.random() * 10));
|
||||||
|
const services = env('CONSUL_SERVICE_COUNT', Math.floor(Math.random() * 10));
|
||||||
|
const checks = env('CONSUL_CHECK_COUNT', Math.floor(Math.random() * 10));
|
||||||
|
|
||||||
|
const partitions = range(partitionCount).map((_, i) => `${fake.hacker.noun()}-partition-${i}`);
|
||||||
|
const nspaces = range(nspaceCount).map((_, i) => `${fake.hacker.noun()}-nspace-${i}`);
|
||||||
|
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
${partitions.map(partition => {
|
||||||
|
const health = healthOf(nodes);
|
||||||
|
return nspaces.map(nspace => `
|
||||||
|
{
|
||||||
|
"Total": ${nodes},
|
||||||
|
"Passing": ${health.Passing},
|
||||||
|
"Warning": ${health.Warning},
|
||||||
|
"Critical": ${health.Critical},
|
||||||
|
"Partition": "${partition}",
|
||||||
|
"Namespace": "${nspace}"
|
||||||
|
}
|
||||||
|
`)}).flat()}
|
||||||
|
],
|
||||||
|
"Services": [
|
||||||
|
${partitions.map((partition, i) => {
|
||||||
|
const health = healthOf(services);
|
||||||
|
return nspaces.map((nspace, j) => `
|
||||||
|
{
|
||||||
|
"Name": "${fake.hacker.noun()}-service-${i * j}",
|
||||||
|
"Total": ${services},
|
||||||
|
"Passing": ${health.Passing},
|
||||||
|
"Warning": ${health.Warning},
|
||||||
|
"Critical": ${health.Critical},
|
||||||
|
"Partition": "${partition}",
|
||||||
|
"Namespace": "${nspace}"
|
||||||
|
}
|
||||||
|
`)}).flat()}
|
||||||
|
],
|
||||||
|
"Checks": [
|
||||||
|
${partitions.map((partition, i) => {
|
||||||
|
const health = healthOf(checks);
|
||||||
|
return nspaces.map((nspace, j) => `
|
||||||
|
{
|
||||||
|
"Name": "${fake.hacker.noun()}-check-${i * j}",
|
||||||
|
"Total": ${services},
|
||||||
|
"Passing": ${health.Passing},
|
||||||
|
"Warning": ${health.Warning},
|
||||||
|
"Critical": ${health.Critical},
|
||||||
|
"Partition": "${partition}",
|
||||||
|
"Namespace": "${nspace}"
|
||||||
|
}
|
||||||
|
`)}).flat()}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
})}
|
|
@ -0,0 +1,37 @@
|
||||||
|
${[0].map(_ => {
|
||||||
|
const servers = range(env('CONSUL_SERVER_COUNT', 3)).map(_ => fake.random.uuid());
|
||||||
|
const failureTolerance = Math.ceil(servers.length / 2);
|
||||||
|
const optimisticTolerance = failureTolerance; // <== same for now
|
||||||
|
const leader = fake.random.number({min: 0, max: servers.length - 1});
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
"Healthy": true,
|
||||||
|
"FailureTolerance": ${failureTolerance},
|
||||||
|
"OptimisticFailureTolerance": ${optimisticTolerance},
|
||||||
|
"Servers": {${servers.map((item, i, items) => `
|
||||||
|
"${item}": {
|
||||||
|
"ID": "${item}",
|
||||||
|
"Name": "node-${i}",
|
||||||
|
"Address": "${fake.internet.ip()}:${fake.random.number({min: 0, max: 65535})}",
|
||||||
|
"NodeStatus": "alive",
|
||||||
|
"Version": "1.11.2",
|
||||||
|
"LastContact": "0s",
|
||||||
|
"LastTerm": 2,
|
||||||
|
"LastIndex": 91,
|
||||||
|
"Healthy": true,
|
||||||
|
"StableSince": "2022-02-02T11:59:01.0708146Z",
|
||||||
|
"ReadReplica": false,
|
||||||
|
"Status": "${i === leader ? `leader` : `voter`}",
|
||||||
|
"Meta": {
|
||||||
|
"consul-network-segment": ""
|
||||||
|
},
|
||||||
|
"NodeType": "voter"
|
||||||
|
}
|
||||||
|
`)}},
|
||||||
|
"Leader": "${servers[leader]}",
|
||||||
|
"Voters": [
|
||||||
|
${servers.map(item => `"${item}"`)}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
})}
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"Valid": ${fake.random.boolean()},
|
||||||
|
"License": {
|
||||||
|
"license_id": "${fake.random.uuid()}",
|
||||||
|
"customer_id": "${fake.random.uuid()}",
|
||||||
|
"installation_id": "*",
|
||||||
|
"issue_time": "2021-01-13T15:25:19.052900132Z",
|
||||||
|
"start_time": "2021-01-13T00:00:00Z",
|
||||||
|
"expiration_time": "${env('CONSUL_LICENSE_EXPIRATION', '2022-01-13T23:59:59.999Z')}",
|
||||||
|
"termination_time": "${env('CONSUL_LICENSE_TERMINATION', '2022-01-13T23:59:59.999Z')}",
|
||||||
|
"product": "consul",
|
||||||
|
"flags": {
|
||||||
|
"modules": [
|
||||||
|
"global-visibility-routing-scale",
|
||||||
|
"governance-policy"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
"Global Visibility, Routing and Scale",
|
||||||
|
"Governance and Policy"
|
||||||
|
],
|
||||||
|
"features": [
|
||||||
|
"Automated Backups",
|
||||||
|
"Automated Upgrades",
|
||||||
|
"Enhanced Read Scalability",
|
||||||
|
"Network Segments",
|
||||||
|
"Redundancy Zone",
|
||||||
|
"Advanced Network Federation",
|
||||||
|
"Namespaces",
|
||||||
|
"SSO",
|
||||||
|
"Audit Logging",
|
||||||
|
"Admin Partitions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Warnings": [
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import { setupTest } from 'ember-qunit';
|
|
||||||
module('Integration | Adapter | dc', function(hooks) {
|
|
||||||
setupTest(hooks);
|
|
||||||
test('requestForQuery returns the correct url', function(assert) {
|
|
||||||
const adapter = this.owner.lookup('adapter:dc');
|
|
||||||
const client = this.owner.lookup('service:client/http');
|
|
||||||
const request = client.url.bind(client);
|
|
||||||
const expected = `GET /v1/catalog/datacenters`;
|
|
||||||
const actual = adapter.requestForQuery(request);
|
|
||||||
assert.equal(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import { setupTest } from 'ember-qunit';
|
|
||||||
import { get } from 'consul-ui/tests/helpers/api';
|
|
||||||
import {
|
|
||||||
HEADERS_DEFAULT_ACL_POLICY as DEFAULT_ACL_POLICY,
|
|
||||||
} from 'consul-ui/utils/http/consul';
|
|
||||||
module('Integration | Serializer | dc', function(hooks) {
|
|
||||||
setupTest(hooks);
|
|
||||||
test('respondForQuery returns the correct data for list endpoint', function(assert) {
|
|
||||||
const serializer = this.owner.lookup('serializer:dc');
|
|
||||||
let env = this.owner.lookup('service:env');
|
|
||||||
env = env.var.bind(env);
|
|
||||||
const request = {
|
|
||||||
url: `/v1/catalog/datacenters`,
|
|
||||||
};
|
|
||||||
return get(request.url).then(function(payload) {
|
|
||||||
const ALLOW = 'allow';
|
|
||||||
const expected = payload.map(item => (
|
|
||||||
{
|
|
||||||
Name: item,
|
|
||||||
Datacenter: '',
|
|
||||||
Local: item === env('CONSUL_DATACENTER_LOCAL'),
|
|
||||||
Primary: item === env('CONSUL_DATACENTER_PRIMARY'),
|
|
||||||
DefaultACLPolicy: ALLOW
|
|
||||||
}
|
|
||||||
))
|
|
||||||
const actual = serializer.respondForQuery(function(cb) {
|
|
||||||
const headers = {
|
|
||||||
[DEFAULT_ACL_POLICY]: ALLOW
|
|
||||||
};
|
|
||||||
return cb(headers, payload);
|
|
||||||
}, {
|
|
||||||
dc: '*',
|
|
||||||
});
|
|
||||||
actual.forEach((item, i) => {
|
|
||||||
assert.equal(actual[i].Name, expected[i].Name);
|
|
||||||
assert.equal(actual[i].Local, expected[i].Local);
|
|
||||||
assert.equal(actual[i].Primary, expected[i].Primary);
|
|
||||||
assert.equal(actual[i].DefaultACLPolicy, expected[i].DefaultACLPolicy);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { moduleFor, test } from 'ember-qunit';
|
import { moduleFor } from 'ember-qunit';
|
||||||
import { skip } from 'qunit';
|
import { skip } from 'qunit';
|
||||||
import repo from 'consul-ui/tests/helpers/repo';
|
import repo from 'consul-ui/tests/helpers/repo';
|
||||||
const NAME = 'dc';
|
const NAME = 'dc';
|
||||||
|
@ -7,7 +7,7 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
|
||||||
integration: true,
|
integration: true,
|
||||||
});
|
});
|
||||||
skip("findBySlug (doesn't interact with the API) but still needs an int test");
|
skip("findBySlug (doesn't interact with the API) but still needs an int test");
|
||||||
test('findAll returns the correct data for list endpoint', function(assert) {
|
skip('findAll returns the correct data for list endpoint', function(assert) {
|
||||||
return repo(
|
return repo(
|
||||||
'Dc',
|
'Dc',
|
||||||
'findAll',
|
'findAll',
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import { setupTest } from 'ember-qunit';
|
|
||||||
|
|
||||||
module('Unit | Adapter | dc', function(hooks) {
|
|
||||||
setupTest(hooks);
|
|
||||||
|
|
||||||
// Replace this with your real tests.
|
|
||||||
test('it exists', function(assert) {
|
|
||||||
let adapter = this.owner.lookup('adapter:dc');
|
|
||||||
assert.ok(adapter);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { module, test } from 'qunit';
|
|
||||||
import { setupTest } from 'ember-qunit';
|
|
||||||
import { run } from '@ember/runloop';
|
|
||||||
|
|
||||||
module('Unit | Serializer | dc', function(hooks) {
|
|
||||||
setupTest(hooks);
|
|
||||||
|
|
||||||
// Replace this with your real tests.
|
|
||||||
test('it exists', function(assert) {
|
|
||||||
let store = this.owner.lookup('service:store');
|
|
||||||
let serializer = store.serializerFor('dc');
|
|
||||||
|
|
||||||
assert.ok(serializer);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it serializes records', function(assert) {
|
|
||||||
let store = this.owner.lookup('service:store');
|
|
||||||
let record = run(() => store.createRecord('dc', {}));
|
|
||||||
|
|
||||||
let serializedRecord = record.serialize();
|
|
||||||
|
|
||||||
assert.ok(serializedRecord);
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue