mirror of https://github.com/hashicorp/consul
215 lines
6.2 KiB
JavaScript
215 lines
6.2 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import Error from '@ember/error';
|
|
import { inject as service } from '@ember/service';
|
|
import RepositoryService from 'consul-ui/services/repository';
|
|
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);
|
|
};
|
|
|
|
export default class DcService extends RepositoryService {
|
|
@service('env') env;
|
|
|
|
getModelName() {
|
|
return MODEL_NAME;
|
|
}
|
|
|
|
@dataSource('/:partition/:ns/:dc/datacenters')
|
|
async fetchAll({ partition, ns, dc }, { uri }, request) {
|
|
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')
|
|
async fetch({ partition, ns, dc }, { uri }, request) {
|
|
return (
|
|
await request`
|
|
GET /v1/operator/autopilot/state?${{ dc }}
|
|
X-Request-ID: ${uri}
|
|
`
|
|
)((headers, body, cache) => {
|
|
// turn servers into an array instead of a map/object
|
|
const servers = Object.values(body.Servers);
|
|
const grouped = [];
|
|
return {
|
|
meta: {
|
|
version: 2,
|
|
uri: uri,
|
|
},
|
|
body: cache(
|
|
{
|
|
...body,
|
|
// all servers
|
|
Servers: servers,
|
|
RedundancyZones: Object.entries(body.RedundancyZones || {}).map(([key, value]) => {
|
|
const zone = {
|
|
...value,
|
|
Name: key,
|
|
Healthy: true,
|
|
// convert the string[] to Server[]
|
|
Servers: value.Servers.reduce((prev, item) => {
|
|
const server = body.Servers[item];
|
|
// keep a record of things
|
|
grouped.push(server.ID);
|
|
prev.push(server);
|
|
return prev;
|
|
}, []),
|
|
};
|
|
return zone;
|
|
}),
|
|
ReadReplicas: (body.ReadReplicas || []).map((item) => {
|
|
// keep a record of things
|
|
grouped.push(item);
|
|
return body.Servers[item];
|
|
}),
|
|
Default: {
|
|
Servers: servers.filter((item) => !grouped.includes(item.ID)),
|
|
},
|
|
},
|
|
(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 item = items.findBy('Name', params.name);
|
|
if (typeof item === 'undefined') {
|
|
// TODO: We should use a HTTPError error here and remove all occurances of
|
|
// the custom shaped ember-data error throughout the app
|
|
const e = new Error('Page not found');
|
|
e.status = '404';
|
|
throw { errors: [e] };
|
|
}
|
|
return item;
|
|
}
|
|
}
|