Skip to content

Commit

Permalink
Label for the Web UI (#16006)
Browse files Browse the repository at this point in the history
* Demoable state

* Demo mirage color

* Label as a block with foreground and background colours

* Test mock updates

* Go test updated

* Documentation update for label support
  • Loading branch information
philrenaud authored Feb 2, 2023
1 parent ba20138 commit d71fc95
Show file tree
Hide file tree
Showing 21 changed files with 135 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .changelog/16006.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Added a ui.label block to agent config, letting operators set a visual label and color for their Nomad instance
```
46 changes: 46 additions & 0 deletions nomad/structs/config/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ type UIConfig struct {

// Vault configures deep links for Vault UI
Vault *VaultUIConfig `hcl:"vault"`

// Label configures UI label styles
Label *LabelUIConfig `hcl:"label"`
}

// ConsulUIConfig configures deep links to this cluster's Consul
Expand All @@ -30,13 +33,21 @@ type VaultUIConfig struct {
BaseUIURL string `hcl:"ui_url"`
}

// Label configures UI label styles
type LabelUIConfig struct {
Text string `hcl:"text"`
BackgroundColor string `hcl:"background_color"`
TextColor string `hcl:"text_color"`
}

// DefaultUIConfig returns the canonical defaults for the Nomad
// `ui` configuration.
func DefaultUIConfig() *UIConfig {
return &UIConfig{
Enabled: true,
Consul: &ConsulUIConfig{},
Vault: &VaultUIConfig{},
Label: &LabelUIConfig{},
}
}

Expand Down Expand Up @@ -69,6 +80,7 @@ func (old *UIConfig) Merge(other *UIConfig) *UIConfig {
result.Enabled = other.Enabled
result.Consul = result.Consul.Merge(other.Consul)
result.Vault = result.Vault.Merge(other.Vault)
result.Label = result.Label.Merge(other.Label)

return result
}
Expand Down Expand Up @@ -128,3 +140,37 @@ func (old *VaultUIConfig) Merge(other *VaultUIConfig) *VaultUIConfig {
}
return result
}

// Copy returns a copy of this Label UI config.
func (old *LabelUIConfig) Copy() *LabelUIConfig {
if old == nil {
return nil
}

nc := new(LabelUIConfig)
*nc = *old
return nc
}

// Merge returns a new Label UI configuration by merging another Label UI
// configuration into this one
func (old *LabelUIConfig) Merge(other *LabelUIConfig) *LabelUIConfig {
result := old.Copy()
if result == nil {
result = &LabelUIConfig{}
}
if other == nil {
return result
}

if other.Text != "" {
result.Text = other.Text
}
if other.BackgroundColor != "" {
result.BackgroundColor = other.BackgroundColor
}
if other.TextColor != "" {
result.TextColor = other.TextColor
}
return result
}
6 changes: 6 additions & 0 deletions nomad/structs/config/ui_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func TestUIConfig_Merge(t *testing.T) {
Vault: &VaultUIConfig{
BaseUIURL: "http://vault.example.com:8200",
},
Label: &LabelUIConfig{
Text: "Example Cluster",
BackgroundColor: "blue",
TextColor: "#fff",
},
}

testCases := []struct {
Expand Down Expand Up @@ -64,6 +69,7 @@ func TestUIConfig_Merge(t *testing.T) {
BaseUIURL: "http://consul-other.example.com:8500",
},
Vault: &VaultUIConfig{},
Label: &LabelUIConfig{},
},
},
}
Expand Down
12 changes: 12 additions & 0 deletions ui/app/components/global-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from '@ember/component';
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
import { attributeBindings } from '@ember-decorators/component';
import { htmlSafe } from '@ember/template';

@classic
@attributeBindings('data-test-global-header')
Expand All @@ -22,4 +23,15 @@ export default class GlobalHeader extends Component {
this.system.agent?.get('config.ACL.Enabled') === true
);
}

get labelStyles() {
return htmlSafe(
`
color: ${this.system.agent.get('config')?.UI?.Label?.TextColor};
background-color: ${
this.system.agent.get('config')?.UI?.Label?.BackgroundColor
};
`
);
}
}
9 changes: 9 additions & 0 deletions ui/app/styles/core/navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,13 @@
border-top-color: white;
}
}

.custom-label {
border-radius: 1rem;
padding: 0.25rem 1rem;
background: black;
color: white;
display: grid;
align-self: center;
}
}
1 change: 1 addition & 0 deletions ui/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{{page-title
(if this.system.shouldShowRegions (concat this.system.activeRegion " - "))
(if this.system.agent.config.UI.Label.Text (concat this.system.agent.config.UI.Label.Text " - "))
"Nomad"
separator=" - "
}}
Expand Down
5 changes: 5 additions & 0 deletions ui/app/templates/components/global-header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
<LinkTo @route="jobs" class="navbar-item is-logo" aria-label="Home">
<NomadLogo />
</LinkTo>
{{#if this.system.agent.config.UI.Label}}
<div class="custom-label" style={{this.labelStyles}}>
{{this.system.agent.config.UI.Label.Text}}
</div>
{{/if}}
</div>
{{#if this.system.fuzzySearchEnabled}}
{{! template-lint-disable simple-unless }}
Expand Down
22 changes: 17 additions & 5 deletions ui/mirage/factories/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,28 @@ import { DATACENTERS } from '../common';

const UUIDS = provide(100, faker.random.uuid.bind(faker.random));
const AGENT_STATUSES = ['alive', 'leaving', 'left', 'failed'];
const AGENT_BUILDS = ['1.1.0-beta', '1.0.2-alpha+ent', ...provide(5, faker.system.semver)];
const AGENT_BUILDS = [
'1.1.0-beta',
'1.0.2-alpha+ent',
...provide(5, faker.system.semver),
];

export default Factory.extend({
id: i => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),
id: (i) => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),

name: () => generateName(),

config: {
UI: {
Enabled: true,
Label: {
TextColor: 'white',
BackgroundColor: 'hotpink',
Text: 'Mirage',
},
},
ACL: {
Enabled: true
Enabled: true,
},
Version: {
Version: '1.1.0',
Expand Down Expand Up @@ -59,7 +68,9 @@ export default Factory.extend({
});

function generateName() {
return `nomad@${faker.random.boolean() ? faker.internet.ip() : faker.internet.ipv6()}`;
return `nomad@${
faker.random.boolean() ? faker.internet.ip() : faker.internet.ipv6()
}`;
}

function generateAddress(name) {
Expand All @@ -69,7 +80,8 @@ function generateAddress(name) {
function generateTags(serfPort) {
const rpcPortCandidate = faker.random.number({ min: 4000, max: 4999 });
return {
port: rpcPortCandidate === serfPort ? rpcPortCandidate + 1 : rpcPortCandidate,
port:
rpcPortCandidate === serfPort ? rpcPortCandidate + 1 : rpcPortCandidate,
dc: faker.helpers.randomize(DATACENTERS),
build: faker.helpers.randomize(AGENT_BUILDS),
};
Expand Down
5 changes: 4 additions & 1 deletion ui/tests/acceptance/allocation-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ module('Acceptance | allocation detail', function (hooks) {
);
assert.ok(Allocation.execButton.isPresent);

assert.equal(document.title, `Allocation ${allocation.name} - Nomad`);
assert.equal(
document.title,
`Allocation ${allocation.name} - Mirage - Nomad`
);

await Allocation.details.visitJob();
assert.equal(
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/behaviors/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function browseFilesystem({
`${pathWithLeadingSlash} - ${getTitleComponent({
allocation: this.allocation,
task: this.task,
})} - Nomad`
})} - Mirage - Nomad`
);
assert.equal(
FS.breadcrumbsText,
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/client-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ module('Acceptance | client detail', function (hooks) {
test('/clients/:id should have a breadcrumb trail linking back to clients', async function (assert) {
await ClientDetail.visit({ id: node.id });

assert.equal(document.title, `Client ${node.name} - Nomad`);
assert.equal(document.title, `Client ${node.name} - Mirage - Nomad`);

assert.equal(
Layout.breadcrumbFor('clients.index').text,
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/clients-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module('Acceptance | clients list', function (hooks) {
);
});

assert.equal(document.title, 'Clients - Nomad');
assert.equal(document.title, 'Clients - Mirage - Nomad');
});

test('each client record should show high-level info of the client', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/exec-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ module('Acceptance | exec', function (hooks) {
region: 'region-2',
});

assert.equal(document.title, 'Exec - region-2 - Nomad');
assert.equal(document.title, 'Exec - region-2 - Mirage - Nomad');

assert.equal(Exec.header.region.text, this.job.region);
assert.equal(Exec.header.namespace.text, this.job.namespace);
Expand Down
4 changes: 2 additions & 2 deletions ui/tests/acceptance/regions-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module('Acceptance | regions (only one)', function (hooks) {
await JobsList.visit();

assert.notOk(Layout.navbar.regionSwitcher.isPresent, 'No region switcher');
assert.equal(document.title, 'Jobs - Nomad');
assert.equal(document.title, 'Jobs - Mirage - Nomad');
});

test('when the only region is not named "global", the region switcher still is not shown', async function (assert) {
Expand Down Expand Up @@ -100,7 +100,7 @@ module('Acceptance | regions (many)', function (hooks) {
Layout.navbar.regionSwitcher.isPresent,
'Region switcher is shown'
);
assert.equal(document.title, 'Jobs - global - Nomad');
assert.equal(document.title, 'Jobs - global - Mirage - Nomad');
});

test('when on the default region, pages do not include the region query param', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/server-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module('Acceptance | server detail', function (hooks) {

test('visiting /servers/:server_name', async function (assert) {
assert.equal(currentURL(), `/servers/${encodeURIComponent(agent.name)}`);
assert.equal(document.title, `Server ${agent.name} - Nomad`);
assert.equal(document.title, `Server ${agent.name} - Mirage - Nomad`);
});

test('when the server is the leader, the title shows a leader badge', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/servers-list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ module('Acceptance | servers list', function (hooks) {
);
});

assert.equal(document.title, 'Servers - Nomad');
assert.equal(document.title, 'Servers - Mirage - Nomad');
});

test('each server should show high-level info of the server', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/task-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module('Acceptance | task detail', function (hooks) {

assert.equal(Task.lifecycle, lifecycleName);

assert.equal(document.title, `Task ${task.name} - Nomad`);
assert.equal(document.title, `Task ${task.name} - Mirage - Nomad`);
});

test('breadcrumbs match jobs / job / task group / allocation / task', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/task-group-detail-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ module('Acceptance | task group detail', function (hooks) {

assert.equal(
document.title,
`Task group ${taskGroup.name} - Job ${job.name} - Nomad`
`Task group ${taskGroup.name} - Job ${job.name} - Mirage - Nomad`
);
});

Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/task-logs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module('Acceptance | task logs', function (hooks) {
'No redirect'
);
assert.ok(TaskLogs.hasTaskLog, 'Task log component found');
assert.equal(document.title, `Task ${task.name} logs - Nomad`);
assert.equal(document.title, `Task ${task.name} logs - Mirage - Nomad`);
});

test('the stdout log immediately starts streaming', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/token-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module('Acceptance | tokens', function (hooks) {
null,
'No token secret set'
);
assert.equal(document.title, 'Authorization - Nomad');
assert.equal(document.title, 'Authorization - Mirage - Nomad');

await Tokens.secret(secretId).submit();
assert.equal(
Expand Down
20 changes: 20 additions & 0 deletions website/content/docs/configuration/ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ ui {
vault {
ui_url = "https://vault.example.com:8200/ui"
}
label {
text = "Staging Cluster"
background_color = "yellow"
text_color = "#000000"
}
}
```

Expand All @@ -39,6 +45,9 @@ and the configuration is individual to each agent.
- `vault` <code>([Vault]: nil)</code> - Configures integrations
between the Nomad web UI and the Vault web UI.

- `label` <code>([Label]: nil)</code> - Configures a user-defined
label to display in the Nomad Web UI header.

## `consul` Parameters

- `ui_url` `(string: "")` - Specifies the full base URL to a Consul
Expand All @@ -61,9 +70,20 @@ and the configuration is individual to each agent.
`ui.vault.ui_url` is the URL you'll visit in your browser. If
this field is omitted, this integration will be disabled.

## `label` Parameters

- `text` `(string: "")` - Specifies the text of the label that will be
displayed in the header of the Web UI.
- `background_color` `(string: "")` - The background color of the label to
be displayed. The Web UI will default to a black background.
- `text_color` `(string: "")` - The text color of the label to be displayed.
The Web UI will default to white text.



[web UI]: /nomad/tutorials/web-ui
[Consul]: /nomad/docs/configuration/ui#consul-parameters
[Vault]: /nomad/docs/configuration/ui#vault-parameters
[Label]: /nomad/docs/configuration/ui#label-parameters
[`consul.address`]: /nomad/docs/configuration/consul#address
[`vault.address`]: /nomad/docs/configuration/vault#address

0 comments on commit d71fc95

Please sign in to comment.