mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
[ui] Show ALL regions' leaders when viewing servers route (#24723)
* Looks up all regions' leaders when viewing servers route * Tests for multi-region leadership badges and css same-line fix
This commit is contained in:
3
.changelog/24723.txt
Normal file
3
.changelog/24723.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
ui: add leadership status for servers in other regions
|
||||||
|
```
|
||||||
@@ -7,10 +7,10 @@ import { inject as service } from '@ember/service';
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import Model from '@ember-data/model';
|
import Model from '@ember-data/model';
|
||||||
import { attr } from '@ember-data/model';
|
import { attr } from '@ember-data/model';
|
||||||
import classic from 'ember-classic-decorator';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { action } from '@ember/object';
|
||||||
import formatHost from 'nomad-ui/utils/format-host';
|
import formatHost from 'nomad-ui/utils/format-host';
|
||||||
|
|
||||||
@classic
|
|
||||||
export default class Agent extends Model {
|
export default class Agent extends Model {
|
||||||
@service system;
|
@service system;
|
||||||
|
|
||||||
@@ -29,9 +29,12 @@ export default class Agent extends Model {
|
|||||||
return formatHost(address, rpcPort);
|
return formatHost(address, rpcPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed('rpcAddr', 'system.leader.rpcAddr')
|
@tracked isLeader = false;
|
||||||
get isLeader() {
|
|
||||||
return this.get('system.leader.rpcAddr') === this.rpcAddr;
|
@action async checkForLeadership() {
|
||||||
|
const leaders = await this.system.leaders;
|
||||||
|
this.isLeader = leaders.includes(this.rpcAddr);
|
||||||
|
return this.isLeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed('tags.build')
|
@computed('tags.build')
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ import classic from 'ember-classic-decorator';
|
|||||||
@classic
|
@classic
|
||||||
export default class ClientsRoute extends Route.extend(WithForbiddenState) {
|
export default class ClientsRoute extends Route.extend(WithForbiddenState) {
|
||||||
@service store;
|
@service store;
|
||||||
@service system;
|
|
||||||
|
|
||||||
beforeModel() {
|
|
||||||
return this.get('system.leader');
|
|
||||||
}
|
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
|
|||||||
@@ -15,14 +15,16 @@ export default class ServersRoute extends Route.extend(WithForbiddenState) {
|
|||||||
@service store;
|
@service store;
|
||||||
@service system;
|
@service system;
|
||||||
|
|
||||||
beforeModel() {
|
async beforeModel() {
|
||||||
return this.get('system.leader');
|
await this.system.leaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
model() {
|
async model() {
|
||||||
|
const agents = await this.store.findAll('agent');
|
||||||
|
await Promise.all(agents.map((agent) => agent.checkForLeadership()));
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
nodes: this.store.findAll('node'),
|
nodes: this.store.findAll('node'),
|
||||||
agents: this.store.findAll('agent'),
|
agents,
|
||||||
}).catch(notifyForbidden(this));
|
}).catch(notifyForbidden(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,27 +12,23 @@ import { namespace } from '../adapters/application';
|
|||||||
import jsonWithDefault from '../utils/json-with-default';
|
import jsonWithDefault from '../utils/json-with-default';
|
||||||
import classic from 'ember-classic-decorator';
|
import classic from 'ember-classic-decorator';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
@classic
|
@classic
|
||||||
export default class SystemService extends Service {
|
export default class SystemService extends Service {
|
||||||
@service token;
|
@service token;
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
@computed('activeRegion')
|
/**
|
||||||
get leader() {
|
* Iterates over all regions and returns a list of leaders' rpcAddrs
|
||||||
const token = this.token;
|
*/
|
||||||
|
@computed('regions.[]')
|
||||||
return PromiseObject.create({
|
get leaders() {
|
||||||
promise: token
|
return Promise.all(
|
||||||
.authorizedRequest(`/${namespace}/status/leader`)
|
this.regions.map((region) => {
|
||||||
.then((res) => res.json())
|
return this.token
|
||||||
.then((rpcAddr) => ({ rpcAddr }))
|
.authorizedRequest(`/${namespace}/status/leader?region=${region}`)
|
||||||
.then((leader) => {
|
.then((res) => res.json());
|
||||||
// Dirty self so leader can be used as a dependent key
|
})
|
||||||
this.notifyPropertyChange('leader.rpcAddr');
|
);
|
||||||
return leader;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
|
|||||||
@@ -110,7 +110,8 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.node-status-badges {
|
&.node-status-badges,
|
||||||
|
&.server-status-badges {
|
||||||
.hds-badge__text {
|
.hds-badge__text {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
~}}
|
~}}
|
||||||
|
|
||||||
<td data-test-server-name
|
<td data-test-server-name
|
||||||
{{keyboard-shortcut
|
{{keyboard-shortcut
|
||||||
enumerated=true
|
enumerated=true
|
||||||
action=(action this.goToAgent)
|
action=(action this.goToAgent)
|
||||||
}}
|
}}
|
||||||
@@ -16,14 +16,22 @@
|
|||||||
@size="large"
|
@size="large"
|
||||||
/>
|
/>
|
||||||
</span></td>
|
</span></td>
|
||||||
<td data-test-server-is-leader>
|
<td data-test-server-is-leader class="server-status-badges">
|
||||||
|
<Hds::Badge
|
||||||
<Hds::Badge
|
@text={{if
|
||||||
@text={{if this.agent.isLeader "True" "False"}}
|
this.agent.isLeader
|
||||||
@icon={{if this.agent.isLeader "check-circle" ""}}
|
(if
|
||||||
@color={{if this.agent.isLeader "success" "neutral"}}
|
this.agent.system.shouldShowRegions
|
||||||
@size="large"
|
(concat "True" " (" this.agent.region ")")
|
||||||
/>
|
"True"
|
||||||
|
)
|
||||||
|
"False"
|
||||||
|
}}
|
||||||
|
@icon={{if this.agent.isLeader "check-circle" ""}}
|
||||||
|
@color={{if this.agent.isLeader "success" "neutral"}}
|
||||||
|
@size="large"
|
||||||
|
class="no-wrap"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td data-test-server-address class="is-200px is-truncatable">{{this.agent.address}}</td>
|
<td data-test-server-address class="is-200px is-truncatable">{{this.agent.address}}</td>
|
||||||
<td data-test-server-port>{{this.agent.serfPort}}</td>
|
<td data-test-server-port>{{this.agent.serfPort}}</td>
|
||||||
|
|||||||
@@ -13,8 +13,14 @@ import { copy } from 'ember-copy';
|
|||||||
import formatHost from 'nomad-ui/utils/format-host';
|
import formatHost from 'nomad-ui/utils/format-host';
|
||||||
import faker from 'nomad-ui/mirage/faker';
|
import faker from 'nomad-ui/mirage/faker';
|
||||||
|
|
||||||
export function findLeader(schema) {
|
export function findLeader(schema, region = null) {
|
||||||
const agent = schema.agents.first();
|
let agent;
|
||||||
|
let agents = schema.agents.all().models;
|
||||||
|
if (region) {
|
||||||
|
agent = agents.find((agent) => agent.member?.Tags?.region === region);
|
||||||
|
} else {
|
||||||
|
agent = agents[0];
|
||||||
|
}
|
||||||
return formatHost(agent.member.Address, agent.member.Tags.port);
|
return formatHost(agent.member.Address, agent.member.Tags.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,8 +747,9 @@ export default function () {
|
|||||||
return logEncode(logFrames, logFrames.length - 1);
|
return logEncode(logFrames, logFrames.length - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/status/leader', function (schema) {
|
this.get('/status/leader', function (schema, { queryParams: { region } }) {
|
||||||
return JSON.stringify(findLeader(schema));
|
let leader = JSON.stringify(findLeader(schema, region));
|
||||||
|
return leader;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/acl/tokens', function ({ tokens }, req) {
|
this.get('/acl/tokens', function ({ tokens }, req) {
|
||||||
|
|||||||
@@ -90,5 +90,6 @@ function generateTags(serfPort) {
|
|||||||
rpcPortCandidate === serfPort ? rpcPortCandidate + 1 : rpcPortCandidate,
|
rpcPortCandidate === serfPort ? rpcPortCandidate + 1 : rpcPortCandidate,
|
||||||
dc: faker.helpers.randomize(DATACENTERS),
|
dc: faker.helpers.randomize(DATACENTERS),
|
||||||
build: faker.helpers.randomize(AGENT_BUILDS),
|
build: faker.helpers.randomize(AGENT_BUILDS),
|
||||||
|
region: 'global',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ function smallCluster(server) {
|
|||||||
server.create('feature', { name: 'Dynamic Application Sizing' });
|
server.create('feature', { name: 'Dynamic Application Sizing' });
|
||||||
server.create('feature', { name: 'Sentinel Policies' });
|
server.create('feature', { name: 'Sentinel Policies' });
|
||||||
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
|
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
|
||||||
|
if (withRegions) {
|
||||||
|
server.db.agents[0].member.Tags.region = server.db.regions[0].id;
|
||||||
|
}
|
||||||
server.createList('node-pool', 2);
|
server.createList('node-pool', 2);
|
||||||
server.createList('node', 5);
|
server.createList('node', 5);
|
||||||
server.create(
|
server.create(
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ module('Acceptance | servers list', function (hooks) {
|
|||||||
setupApplicationTest(hooks);
|
setupApplicationTest(hooks);
|
||||||
setupMirage(hooks);
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
server.create('region', { id: 'global' });
|
||||||
|
});
|
||||||
|
|
||||||
test('it passes an accessibility audit', async function (assert) {
|
test('it passes an accessibility audit', async function (assert) {
|
||||||
minimumSetup();
|
minimumSetup();
|
||||||
await ServersList.visit();
|
await ServersList.visit();
|
||||||
@@ -51,7 +55,6 @@ module('Acceptance | servers list', function (hooks) {
|
|||||||
const sortedAgents = server.db.agents.sort(agentSort(leader)).reverse();
|
const sortedAgents = server.db.agents.sort(agentSort(leader)).reverse();
|
||||||
|
|
||||||
await ServersList.visit();
|
await ServersList.visit();
|
||||||
|
|
||||||
await percySnapshot(assert);
|
await percySnapshot(assert);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@@ -116,4 +119,31 @@ module('Acceptance | servers list', function (hooks) {
|
|||||||
await ServersList.error.seekHelp();
|
await ServersList.error.seekHelp();
|
||||||
assert.equal(currentURL(), '/settings/tokens');
|
assert.equal(currentURL(), '/settings/tokens');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('multiple regions should each show leadership values', async function (assert) {
|
||||||
|
server.createList('node-pool', 1);
|
||||||
|
server.createList('node', 1);
|
||||||
|
server.create('region', { id: 'global' });
|
||||||
|
server.create('region', { id: 'galactic' });
|
||||||
|
server.createList('agent', 3);
|
||||||
|
server.db.agents[0].member.Tags.region = 'global';
|
||||||
|
server.db.agents[1].member.Tags.region = 'galactic';
|
||||||
|
server.db.agents[2].member.Tags.region = 'galactic';
|
||||||
|
await ServersList.visit();
|
||||||
|
assert.equal(
|
||||||
|
ServersList.servers.objectAt(0).leader,
|
||||||
|
'True (galactic)',
|
||||||
|
'Leadership is shown for the galactic region'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
ServersList.servers.objectAt(1).leader,
|
||||||
|
'True (global)',
|
||||||
|
'Leadership is shown for the global region'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
ServersList.servers.objectAt(2).leader,
|
||||||
|
'False',
|
||||||
|
'Non-leader servers are shown'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user