Upgrade Ember and friends 3.28 (#12215)

* chore: upgrade forward compatible packages

* chore: v3.20.2...v3.24.0

* chore: silence string prototype extension deprecation

* refact: don't test clicking disabled button job-list

Recent test-helper upgrades will guard against clicking disabled buttons
as this is not something that real users can do. We need to change our
tests accordingly.

* fix: await async test helper `expectError`

We have to await this async test function otherwise the test's
rendering context will be torn down before we run assertions
against it.

* fix: don't try to click disabled two-step-button

Recent test-helper updates prohibit clicking disabled buttons. We need
to adapt the tests accordingly.

* fix: recommendation-accordion

Use up-to-date semantics for handling list-accordion closing
in recommendation-accordion.

* fixes toggling recommendation-accordion toggle.

* fix: simple-unless linting error application.hbs

There's no reason to use unless here - we can use if instead.

* fix: no-quoteless-attributes recommendation accordion

* fix: no-quoteless-attributes recommendation-chart

* fix: allow `unless` - global-header.hbs

This is a valid use of unless in our opinion.

* fix: allow unless in job-diff

This is not a great use for unless but we don't want to change this
behavior atm.

* fix: no-attrs-in-components list-pager

There is no need to use this.attrs in classic components. When we
will convert to glimmer we will use `@`-instead.

* fix: simple-unless job/definition

We can convert to a simple if here.

* fix: allow inline-styles stats-box component

To make linter happy.

* fix: disable no-action and no-invalid-interactive

Will be adressed in follow-up PRs.

* chore: update ember-classic-decorator to latest

* chore: upgrade ember-can to latest

* chore: upgrade ember-composable-helpers to latest

* chore: upgrade ember-concurrency

* fix: recomputation deprecation `Trigger`

schedule `do` on actions queue to work around recomputation deprecation
when triggering Trigger on `did-insert`.

* chore: upgrade ember-cli-string-helpers

* chore: upgrade ember-copy

* chore: upgrade ember-data-model-fragments

* chore: upgrade ember-deprecation-workflow

* chore: upgrade ember-inline-svg

* chore: upgrade ember-modifier

* chore: upgrade ember-truth-helpers

* chore: upgrade ember-moment & ember-cli-moment-shim

* chore: upgrade ember-power-select

* chore: upgrade ember-responsive

* chore: upgrade ember-sinon

* chore: upgrade ember-cli-mirage

For now we will stay on 2.2 - upgrades > 2.3 break the build.

* chore: upgrade 3.24.0 to 3.28.5

* fix: add missing classic decorators on adapters

* fix: missing classic decorators to serializers

* fix: don't reopen Ember.Object anymore

* fix: remove unused useNativeEvents

ember-cli-page-objects doesn't provide this method anymore

* fix: add missing attributeBindings for test-selectors

ember-test-selectors doesn't provides automatic bindings for
data-test-* attributes anymore.

* fix: classic decorator for application serializer test

* fix: remove `removeContext` from tests.

It is unneeded and ember-cli-page-objects doesn't provides
this method anymore.

* fix: remove deprecations `run.*`-invocations

* fix: `collapseWhitespace` in optimize test

* fix: make sure to load async relationship before access

* fix: dependent keys for relationship computeds

We need to add `*.isFulfilled` as dependent keys for computeds that
access async relationships.

* fix: `computed.read`-invocations use `read` instead

* chore: prettify templates

* fix: use map instead of mapBy ember-cli-page-object

Doesn't work with updated ember-cli-page-object anymore.

* fix: remove remaining deprecated `run.*`-calls

* chore: add more deprecations deprecation-workflow

* fix: `implicit-injection`-deprecation

All routes that add watchers will need to inject the store-service
as the store service is internally used in watchers.

* fix: more implicit injection deprecations

* chore: silence implicit-injection deprecation

We can tackle the deprecation when we find the time.

* fix: new linting errors after upgrade

* fix: remove merge conflicts prettierignore

* chore: upgrade to run node 12.22 when building binaries
This commit is contained in:
Michael Klein
2022-03-08 18:28:36 +01:00
committed by GitHub
parent a9d64b8e3e
commit fe6cbbf078
138 changed files with 3495 additions and 3457 deletions

View File

@@ -10,8 +10,8 @@ export NVM_DIR="${HOME}/.nvm"
# Install Node, Ember CLI, and Phantom for UI development
# Use exact full version version (e.g. not 12) for reproducibility purposes
nvm install 12.19.0
nvm alias default 12.19.0
nvm install 12.22.10
nvm alias default 12.22.10
npm install -g ember-cli
# Install Yarn for front-end dependency management

View File

@@ -15,6 +15,8 @@ mirage/
# misc
/coverage/
!.*
.*/
.eslintcache
# ember-try
/.node_modules.ember-try/

View File

@@ -37,15 +37,16 @@ module.exports = {
// node files
{
files: [
'.eslintrc.js',
'.prettierrc.js',
'.template-lintrc.js',
'ember-cli-build.js',
'testem.js',
'blueprints/*/index.js',
'config/**/*.js',
'lib/*/index.js',
'server/**/*.js',
'./.eslintrc.js',
'./.prettierrc.js',
'./.template-lintrc.js',
'./ember-cli-build.js',
'./testem.js',
'./blueprints/*/index.js',
'./config/**/*.js',
'./lib/*/index.js',
'./server/**/*.js',
'./tests/.eslintrc.js',
],
parserOptions: {
sourceType: 'script',
@@ -73,5 +74,10 @@ module.exports = {
},
plugins: ['node'],
},
{
// Test files:
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
},
],
};

View File

@@ -3,37 +3,7 @@
module.exports = {
extends: 'recommended',
rules: {
// should definitely move to template only
// glimmer components for this one
'no-partial': false,
// these need to be looked into, but
// may be a bigger change
'no-invalid-interactive': false,
'simple-unless': false,
'self-closing-void-elements': false,
'no-unnecessary-concat': false,
'no-quoteless-attributes': false,
'no-nested-interactive': false,
// Only used in list-pager, which can be replaced with
// an angle-bracket component
'no-attrs-in-components': false,
// Used in practice with charts. Ideally this would be true
// except for a whitelist of chart files.
'no-inline-styles': false,
// not sure we'll ever want these on,
// would be nice but if prettier isn't doing
// it for us, then not sure it's worth it
'attribute-indentation': false,
'block-indentation': false,
quotes: false,
// remove when moving from extending `recommended` to `octane`
'no-curly-component-invocation': true,
'no-implicit-this': true,
'no-action': 'off',
'no-invalid-interactive': 'off',
},
};

View File

@@ -6,9 +6,9 @@ The official Nomad UI.
This is an [ember.js](https://emberjs.com/) project, and you will need the following tools installed on your computer.
* [Node.js v10](https://nodejs.org/)
* [Yarn](https://yarnpkg.com)
* [Ember CLI](https://ember-cli.com/)
- [Node.js v10](https://nodejs.org/)
- [Yarn](https://yarnpkg.com)
- [Ember CLI](https://ember-cli.com/)
## Installation
@@ -23,8 +23,8 @@ $ yarn
UI in development mode defaults to using fake generated data, but you can configure it to proxy a live running nomad process by setting `USE_MIRAGE` environment variable to `false`. First, make sure nomad is running. The UI, in development mode, runs independently from Nomad, so this could be an official release or a dev branch. Likewise, Nomad can be running in server mode or dev mode. As long as the API is accessible, the UI will work as expected.
* `USE_MIRAGE=false ember serve`
* Visit your app at [http://localhost:4200](http://localhost:4200).
- `USE_MIRAGE=false ember serve`
- Visit your app at [http://localhost:4200](http://localhost:4200).
You may need to reference the direct path to `ember`, typically in `./node_modules/.bin/ember`.
@@ -38,8 +38,8 @@ All necessary tools for UI development are installed as part of the Vagrantfile.
That said, development with Vagrant is still possible, but the `ember serve` command requires two modifications:
* `--watch polling`: This allows the vm to notice file changes made in the host environment.
* `--port 4201`: The default port 4200 is not forwarded, since local development is recommended.
- `--watch polling`: This allows the vm to notice file changes made in the host environment.
- `--port 4201`: The default port 4200 is not forwarded, since local development is recommended.
This makes the full command for running the UI in development mode in Vagrant:
@@ -51,8 +51,8 @@ $ ember serve --watch polling --port 4201
Nomad UI tests can be run independently of Nomad golang tests.
* `ember test` (single run, headless browser)
* `ember test --server` (watches for changes, runs in a full browser)
- `ember test` (single run, headless browser)
- `ember test --server` (watches for changes, runs in a full browser)
You can use `--filter <test name>` to run a targetted set of tests, e.g. `ember test --filter 'allocation detail'`.
@@ -60,18 +60,15 @@ In the test environment, the fake data is generated with a random seed. If you w
### Linting
Linting should happen automatically in your editor and when committing changes, but it can also be invoked manually.
* `npm run lint:hbs`
* `npm run lint:js`
* `npm run lint:js -- --fix`
- `yarn lint`
- `yarn lint:fix`
### Building
Typically `make release` or `make dev-ui` will be the desired build workflow, but in the event that build artifacts need to be inspected, `ember build` will output compiled files in `ui/dist`.
* `ember build` (development)
* `ember build --environment production` (production)
- `ember build` (development)
- `ember build --environment production` (production)
### Releasing
@@ -79,7 +76,7 @@ Nomad UI releases are in lockstep with Nomad releases and are integrated into th
### Conventions
* UI branches should be prefix with `f-ui-` for feature work and `b-ui-` for bug fixes. This instructs CI to skip running nomad backend tests.
- UI branches should be prefix with `f-ui-` for feature work and `b-ui-` for bug fixes. This instructs CI to skip running nomad backend tests.
### Storybook UI Library

View File

@@ -1,5 +1,7 @@
import ApplicationAdapter from './application';
import classic from 'ember-classic-decorator';
@classic
export default class AgentAdapter extends ApplicationAdapter {
pathForType = () => 'agent/members';

View File

@@ -1,6 +1,8 @@
import Watchable from './watchable';
import addToPath from 'nomad-ui/utils/add-to-path';
import classic from 'ember-classic-decorator';
@classic
export default class AllocationAdapter extends Watchable {
stop = adapterAction('/stop');

View File

@@ -1,5 +1,7 @@
import Watchable from './watchable';
import classic from 'ember-classic-decorator';
@classic
export default class DeploymentAdapter extends Watchable {
fail(deployment) {
const id = deployment.get('id');

View File

@@ -1,5 +1,7 @@
import ApplicationAdapter from './application';
import classic from 'ember-classic-decorator';
@classic
export default class EvaluationAdapter extends ApplicationAdapter {
handleResponse(_status, headers) {
const result = super.handleResponse(...arguments);

View File

@@ -1,5 +1,7 @@
import WatchableNamespaceIDs from './watchable-namespace-ids';
import classic from 'ember-classic-decorator';
@classic
export default class JobScaleAdapter extends WatchableNamespaceIDs {
urlForFindRecord(id, type, hash) {
return super.urlForFindRecord(id, 'job', hash, 'scale');

View File

@@ -1,5 +1,7 @@
import WatchableNamespaceIDs from './watchable-namespace-ids';
import classic from 'ember-classic-decorator';
@classic
export default class JobSummaryAdapter extends WatchableNamespaceIDs {
urlForFindRecord(id, type, hash) {
return super.urlForFindRecord(id, 'job', hash, 'summary');

View File

@@ -1,6 +1,8 @@
import ApplicationAdapter from './application';
import addToPath from 'nomad-ui/utils/add-to-path';
import classic from 'ember-classic-decorator';
@classic
export default class JobVersionAdapter extends ApplicationAdapter {
revertTo(jobVersion) {
const jobAdapter = this.store.adapterFor('job');

View File

@@ -1,7 +1,9 @@
import WatchableNamespaceIDs from './watchable-namespace-ids';
import addToPath from 'nomad-ui/utils/add-to-path';
import { base64EncodeString } from 'nomad-ui/utils/encode';
import classic from 'ember-classic-decorator';
@classic
export default class JobAdapter extends WatchableNamespaceIDs {
relationshipFallbackLinks = {
summary: '/summary',

View File

@@ -1,6 +1,8 @@
import Watchable from './watchable';
import codesForError from '../utils/codes-for-error';
import classic from 'ember-classic-decorator';
@classic
export default class NamespaceAdapter extends Watchable {
findRecord(store, modelClass, id) {
return super.findRecord(...arguments).catch((error) => {

View File

@@ -1,6 +1,8 @@
import Watchable from './watchable';
import addToPath from 'nomad-ui/utils/add-to-path';
import classic from 'ember-classic-decorator';
@classic
export default class NodeAdapter extends Watchable {
setEligible(node) {
return this.setEligibility(node, true);

View File

@@ -1,5 +1,7 @@
import Watchable from './watchable';
import classic from 'ember-classic-decorator';
@classic
export default class PluginAdapter extends Watchable {
queryParamsToAttrs = {
type: 'type',

View File

@@ -1,5 +1,7 @@
import { default as ApplicationAdapter, namespace } from './application';
import classic from 'ember-classic-decorator';
@classic
export default class PolicyAdapter extends ApplicationAdapter {
namespace = namespace + '/acl';
}

View File

@@ -1,5 +1,7 @@
import ApplicationAdapter from './application';
import classic from 'ember-classic-decorator';
@classic
export default class RecommendationSummaryAdapter extends ApplicationAdapter {
pathForType = () => 'recommendations';

View File

@@ -1,7 +1,9 @@
import { inject as service } from '@ember/service';
import { default as ApplicationAdapter, namespace } from './application';
import OTTExchangeError from '../utils/ott-exchange-error';
import classic from 'ember-classic-decorator';
@classic
export default class TokenAdapter extends ApplicationAdapter {
@service store;

View File

@@ -1,5 +1,7 @@
import WatchableNamespaceIDs from './watchable-namespace-ids';
import classic from 'ember-classic-decorator';
@classic
export default class VolumeAdapter extends WatchableNamespaceIDs {
queryParamsToAttrs = {
type: 'type',

View File

@@ -5,7 +5,9 @@ import { AbortError } from '@ember-data/adapter/error';
import queryString from 'query-string';
import ApplicationAdapter from './application';
import removeRecord from '../utils/remove-record';
import classic from 'ember-classic-decorator';
@classic
export default class Watchable extends ApplicationAdapter {
@service watchList;
@service store;

View File

@@ -4,16 +4,25 @@ import Component from '@ember/component';
import { computed } from '@ember/object';
import { computed as overridable } from 'ember-overridable-computed';
import { alias } from '@ember/object/computed';
import { run } from '@ember/runloop';
import { scheduleOnce } from '@ember/runloop';
import { task, timeout } from 'ember-concurrency';
import { lazyClick } from '../helpers/lazy-click';
import AllocationStatsTracker from 'nomad-ui/utils/classes/allocation-stats-tracker';
import classic from 'ember-classic-decorator';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
@classic
@tagName('tr')
@classNames('allocation-row', 'is-interactive')
@attributeBindings(
'data-test-allocation',
'data-test-write-allocation',
'data-test-read-allocation'
)
export default class AllocationRow extends Component {
@service store;
@service token;
@@ -56,7 +65,7 @@ export default class AllocationRow extends Component {
const allocation = this.allocation;
if (allocation) {
run.scheduleOnce('afterRender', this, qualifyAllocation);
scheduleOnce('afterRender', this, qualifyAllocation);
} else {
this.fetchStats.cancelAll();
}

View File

@@ -1,6 +1,10 @@
import { computed } from '@ember/object';
import DistributionBar from './distribution-bar';
import { attributeBindings } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@attributeBindings('data-test-allocation-status-bar')
export default class AllocationStatusBar extends DistributionBar {
layoutName = 'components/distribution-bar';

View File

@@ -1,5 +1,9 @@
{{! template-lint-disable no-unknown-arguments-for-builtin-components }}
<li data-test-breadcrumb-default>
<LinkTo @params={{@crumb.args}} data-test-breadcrumb={{@crumb.args.firstObject}}>
<LinkTo
@params={{@crumb.args}}
data-test-breadcrumb={{@crumb.args.firstObject}}
>
{{#if @crumb.title}}
<dl>
<dt>

View File

@@ -1,8 +1,10 @@
import { computed } from '@ember/object';
import DistributionBar from './distribution-bar';
import classic from 'ember-classic-decorator';
import { attributeBindings } from '@ember-decorators/component';
@classic
@attributeBindings('data-test-children-status-bar')
export default class ChildrenStatusBar extends DistributionBar {
layoutName = 'components/distribution-bar';

View File

@@ -1,18 +1,19 @@
{{#if this.show}}
<ListAccordion
data-test-recommendation-accordion
class="recommendation-accordion boxed-section {{if this.closing "closing"}}"
class="recommendation-accordion boxed-section {{if this.closing 'closing'}}"
@source={{array @summary}}
@key="id"
{{did-insert this.inserted}}
as |a|>
as |a|
>
{{#if a.isOpen}}
<div class="animation-container" style={{this.animationContainerStyle}}>
<Das::RecommendationCard
@summary={{@summary}}
@proceed={{this.proceed}}
@onCollapse={{action (mut a.isOpen) false}}
@skipReset=true
@onCollapse={{a.close}}
@skipReset={{true}}
/>
</div>
{{else}}

View File

@@ -1,12 +1,17 @@
{{! template-lint-disable no-duplicate-landmark-elements}}
{{#if this.interstitialComponent}}
<section class="das-interstitial" style={{this.interstitialStyle}}>
{{component (concat 'das/' this.interstitialComponent) proceed=this.proceedPromiseResolve error=this.error}}
{{component
(concat "das/" this.interstitialComponent)
proceed=this.proceedPromiseResolve
error=this.error
}}
</section>
{{else if @summary.taskGroup}}
<section
...attributes
data-test-task-group-recommendations
class='recommendation-card'
class="recommendation-card"
{{did-insert this.cardInserted}}
>
@@ -14,11 +19,18 @@
<header class="overview inner-container">
<h3 class="slug">
<span class="job" data-test-job-name>{{@summary.taskGroup.job.name}}</span>
<span class="group" data-test-task-group-name>{{@summary.taskGroup.name}}</span>
<span
class="job"
data-test-job-name
>{{@summary.taskGroup.job.name}}</span>
<span
class="group"
data-test-task-group-name
>{{@summary.taskGroup.name}}</span>
</h3>
<h4 class="namespace">
<span class="namespace-label">Namespace:</span> <span data-test-namespace>{{@summary.jobNamespace}}</span>
<span class="namespace-label">Namespace:</span>
<span data-test-namespace>{{@summary.jobNamespace}}</span>
</h4>
</header>
@@ -45,10 +57,16 @@
<th class="toggle-cell">
<Toggle
data-test-cpu-toggle
@isActive={{and this.allCpuToggleActive (not this.allCpuToggleDisabled)}}
@isActive={{and
this.allCpuToggleActive
(not this.allCpuToggleDisabled)
}}
@isDisabled={{this.allCpuToggleDisabled}}
@onToggle={{action this.toggleAllRecommendationsForResource 'CPU'}}
title='Toggle CPU recommendations for all tasks'
@onToggle={{action
this.toggleAllRecommendationsForResource
"CPU"
}}
title="Toggle CPU recommendations for all tasks"
>
<div class="label-wrapper">CPU</div>
</Toggle>
@@ -56,10 +74,16 @@
<th class="toggle-cell">
<Toggle
data-test-memory-toggle
@isActive={{and this.allMemoryToggleActive (not this.allMemoryToggleDisabled)}}
@isActive={{and
this.allMemoryToggleActive
(not this.allMemoryToggleDisabled)
}}
@isDisabled={{this.allMemoryToggleDisabled}}
@onToggle={{action this.toggleAllRecommendationsForResource 'MemoryMB'}}
title='Toggle memory recommendations for all tasks'
@onToggle={{action
this.toggleAllRecommendationsForResource
"MemoryMB"
}}
title="Toggle memory recommendations for all tasks"
>
<div class="label-wrapper">Mem</div>
</Toggle>
@@ -87,13 +111,27 @@
</section>
<section class="actions overview inner-container">
<button class='button is-primary' type='button' disabled={{this.cannotAccept}} data-test-accept {{on "click" this.accept}}>Accept</button>
<button class='button is-light' type='button' data-test-dismiss {{on "click" this.dismiss}}>Dismiss</button>
<button
class="button is-primary"
type="button"
disabled={{this.cannotAccept}}
data-test-accept
{{on "click" this.accept}}
>Accept</button>
<button
class="button is-light"
type="button"
data-test-dismiss
{{on "click" this.dismiss}}
>Dismiss</button>
</section>
<section class="active-task-group" data-test-active-task>
<section class="top active-task inner-container">
<CopyButton data-test-copy-button @clipboardText={{this.copyButtonLink}}>
<CopyButton
data-test-copy-button
@clipboardText={{this.copyButtonLink}}
>
{{@summary.taskGroup.job.name}}
/
{{@summary.taskGroup.name}}
@@ -104,7 +142,8 @@
data-test-accordion-toggle
class="button is-light is-compact pull-right accordion-toggle"
{{on "click" @onCollapse}}
type="button">
type="button"
>
Collapse
</button>
{{/if}}
@@ -131,7 +170,10 @@
@currentValue={{recommendation.currentValue}}
@recommendedValue={{recommendation.value}}
@stats={{recommendation.stats}}
@disabled={{includes recommendation @summary.excludedRecommendations}}
@disabled={{includes
recommendation
@summary.excludedRecommendations
}}
/>
</li>
{{/each}}

View File

@@ -194,11 +194,12 @@ export default class DasRecommendationCardComponent extends Component {
}
@action
dismiss() {
async dismiss() {
this.storeCardHeight();
this.args.summary.excludedRecommendations.pushObjects(
this.args.summary.recommendations
);
const recommendations = await this.args.summary.recommendations;
this.args.summary.excludedRecommendations.pushObjects(recommendations);
this.args.summary
.save()
.then(

View File

@@ -31,7 +31,13 @@
</text>
{{#if this.center}}
<line class="center" x1={{this.center.x1}} y1={{this.center.y1}} x2={{this.center.x2}} y2={{this.center.y2}} />
<line
class="center"
x1={{this.center.x1}}
y1={{this.center.y1}}
x2={{this.center.x2}}
y2={{this.center.y2}}
></line>
{{/if}}
{{#each this.statsShapes as |shapes|}}
@@ -55,7 +61,7 @@
height={{shapes.rect.height}}
{{on "mouseenter" (fn this.setActiveLegendRow shapes.text.label)}}
{{on "mouseleave" this.unsetActiveLegendRow}}
/>
></rect>
<line
class="stat {{shapes.class}}"
@@ -65,7 +71,7 @@
y2={{shapes.line.y2}}
{{on "mouseenter" (fn this.setActiveLegendRow shapes.text.label)}}
{{on "mouseleave" this.unsetActiveLegendRow}}
/>
></line>
{{/each}}
{{#unless @disabled}}
@@ -77,24 +83,24 @@
y={{this.deltaRect.y}}
width={{this.deltaRect.width}}
height={{this.deltaRect.height}}
/>
></rect>
<polygon
class="delta"
style={{this.deltaTriangle.style}}
points={{this.deltaTriangle.points}}
/>
></polygon>
<line
class="changes delta"
style={{this.deltaLines.delta.style}}
x1=0
x1={{0}}
y1={{this.edgeTickY1}}
x2=0
x2={{0}}
y2={{this.edgeTickY2}}
{{on "mouseenter" (fn this.setActiveLegendRow "New")}}
{{on "mouseleave" this.unsetActiveLegendRow}}
/>
></line>
<line
class="changes"
@@ -104,7 +110,7 @@
y2={{this.edgeTickY2}}
{{on "mouseenter" (fn this.setActiveLegendRow "Current")}}
{{on "mouseleave" this.unsetActiveLegendRow}}
/>
></line>
<text
class="changes"
@@ -138,10 +144,19 @@
{{/if}}
{{/unless}}
<line class="zero" x1={{this.gutterWidthLeft}} y1={{this.edgeTickY1}} x2={{this.gutterWidthLeft}} y2={{this.edgeTickY2}} />
<line
class="zero"
x1={{this.gutterWidthLeft}}
y1={{this.edgeTickY1}}
x2={{this.gutterWidthLeft}}
y2={{this.edgeTickY2}}
></line>
</svg>
<div class="chart-tooltip {{if this.showLegend "active" "inactive"}}" style={{this.tooltipStyle}}>
<div
class="chart-tooltip {{if this.showLegend 'active' 'inactive'}}"
style={{this.tooltipStyle}}
>
<ol>
{{#each this.sortedStats as |stat|}}
<li class={{if (eq this.activeLegendRow stat.label) "active"}}>

View File

@@ -1,5 +1,5 @@
{{#if @summary.taskGroup.allocations.length}}
{{!-- Prevent storing aggregate diffs until allocation count is known --}}
{{! Prevent storing aggregate diffs until allocation count is known }}
<tr
class="recommendation-row"
...attributes
@@ -8,12 +8,13 @@
>
<td>
<div data-test-slug>
<span class='job'>{{@summary.taskGroup.job.name}}</span>
<span class="job">{{@summary.taskGroup.job.name}}</span>
/
<span class='task-group'>{{@summary.taskGroup.name}}</span>
<span class="task-group">{{@summary.taskGroup.name}}</span>
</div>
<div class='namespace'>
Namespace: <span data-test-namespace>{{@summary.jobNamespace}}</span>
<div class="namespace">
Namespace:
<span data-test-namespace>{{@summary.jobNamespace}}</span>
</div>
</td>
<td data-test-date>
@@ -25,13 +26,13 @@
<td data-test-cpu>
{{#if this.cpu.delta}}
{{this.cpu.signedDiff}}
<span class='percent'>{{this.cpu.percentDiff}}</span>
<span class="percent">{{this.cpu.percentDiff}}</span>
{{/if}}
</td>
<td data-test-memory>
{{#if this.memory.delta}}
{{this.memory.signedDiff}}
<span class='percent'>{{this.memory.percentDiff}}</span>
<span class="percent">{{this.memory.percentDiff}}</span>
{{/if}}
</td>
<td data-test-aggregate-cpu>

View File

@@ -2,7 +2,7 @@
import Component from '@ember/component';
import { computed, set } from '@ember/object';
import { observes } from '@ember-decorators/object';
import { run } from '@ember/runloop';
import { run, once } from '@ember/runloop';
import { assign } from '@ember/polyfills';
import { guidFor } from '@ember/object/internals';
import { copy } from 'ember-copy';
@@ -190,6 +190,6 @@ export default class DistributionBar extends Component.extend(WindowResizable) {
/* eslint-enable */
windowResizeHandler() {
run.once(this, this.renderChart);
once(this, this.renderChart);
}
}

View File

@@ -1,6 +1,6 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { run } from '@ember/runloop';
import { next } from '@ember/runloop';
import { action } from '@ember/object';
import { minIndex, max } from 'd3-array';
@@ -14,7 +14,7 @@ export default class FlexMasonry extends Component {
@action
reflow() {
run.next(() => {
next(() => {
// There's nothing to do if there is no element
if (!this.element) return;

View File

@@ -1,12 +1,17 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { isEmpty } from '@ember/utils';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@tagName('nav')
@classNames('breadcrumb')
@attributeBindings('data-test-fs-breadcrumbs')
export default class Breadcrumbs extends Component {
'data-test-fs-breadcrumbs' = true;

View File

@@ -6,11 +6,12 @@ import { equal, gt } from '@ember/object/computed';
import RSVP from 'rsvp';
import Log from 'nomad-ui/utils/classes/log';
import timeout from 'nomad-ui/utils/timeout';
import { classNames } from '@ember-decorators/component';
import { classNames, attributeBindings } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@classNames('boxed-section', 'task-log')
@attributeBindings('data-test-file-viewer')
export default class File extends Component {
@service token;
@service system;

View File

@@ -2,7 +2,7 @@ import Component from '@ember/component';
import { computed } from '@ember/object';
import { assert } from '@ember/debug';
import { guidFor } from '@ember/object/internals';
import { run } from '@ember/runloop';
import { once } from '@ember/runloop';
import d3Shape from 'd3-shape';
import WindowResizable from 'nomad-ui/mixins/window-resizable';
import { classNames } from '@ember-decorators/component';
@@ -88,6 +88,6 @@ export default class GaugeChart extends Component.extend(WindowResizable) {
}
windowResizeHandler() {
run.once(this, this.updateDimensions);
once(this, this.updateDimensions);
}
}

View File

@@ -1,8 +1,10 @@
import Component from '@ember/component';
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
import { attributeBindings } from '@ember-decorators/component';
@classic
@attributeBindings('data-test-global-header')
export default class GlobalHeader extends Component {
@service config;
@service system;

View File

@@ -1,14 +1,15 @@
import Component from '@ember/component';
import { classNames } from '@ember-decorators/component';
import { classNames, attributeBindings } from '@ember-decorators/component';
import { task } from 'ember-concurrency';
import { action, set } from '@ember/object';
import { inject as service } from '@ember/service';
import { debounce, run } from '@ember/runloop';
import { debounce, next } from '@ember/runloop';
const SLASH_KEY = '/';
const MAXIMUM_RESULTS = 10;
@classNames('global-search-container')
@attributeBindings('data-test-search-parent')
export default class GlobalSearchControl extends Component {
@service router;
@service token;
@@ -223,7 +224,7 @@ export default class GlobalSearchControl extends Component {
@action
onCloseEvent(select, event) {
if (event.key === 'Escape') {
run.next(() => {
next(() => {
this.element.querySelector('.ember-power-select-trigger').blur();
});
}

View File

@@ -1,11 +1,16 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@tagName('figure')
@classNames('image-file')
@attributeBindings('data-test-image-file')
export default class ImageFile extends Component {
'data-test-image-file' = true;

View File

@@ -1,8 +1,10 @@
import { computed } from '@ember/object';
import DistributionBar from './distribution-bar';
import { attributeBindings } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@attributeBindings('data-test-job-client-status-bar')
export default class JobClientStatusBar extends DistributionBar {
layoutName = 'components/distribution-bar';

View File

@@ -1,13 +1,15 @@
import Component from '@ember/component';
import { assert } from '@ember/debug';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { computed, action } from '@ember/object';
import { task } from 'ember-concurrency';
import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error';
import localStorageProperty from 'nomad-ui/utils/properties/local-storage';
import { attributeBindings } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@attributeBindings('data-test-job-editor')
export default class JobEditor extends Component {
@service store;
@service config;
@@ -33,6 +35,12 @@ export default class JobEditor extends Component {
this.set('_context', value);
}
@action updateCode(value) {
if (!this.job.isDestroying && !this.job.isDestroyed) {
this.job.set('_newDefinition', value);
}
}
_context = null;
parseError = null;
planError = null;

View File

@@ -2,12 +2,17 @@ import Component from '@ember/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { lazyClick } from '../helpers/lazy-click';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@tagName('tr')
@classNames('job-row', 'is-interactive')
@attributeBindings('data-test-job-row')
export default class JobRow extends Component {
@service router;
@service store;

View File

@@ -1,7 +1,7 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { run } from '@ember/runloop';
import { schedule, next } from '@ember/runloop';
import d3 from 'd3-selection';
import d3Scale from 'd3-scale';
import d3Axis from 'd3-axis';
@@ -235,7 +235,7 @@ export default class LineChart extends Component {
const mouseX = d3.pointer(ev, this)[0];
chart.latestMouseX = mouseX;
updateActiveDatum(mouseX);
run.schedule('afterRender', chart, () => (chart.isActive = true));
schedule('afterRender', chart, () => (chart.isActive = true));
});
canvas.on('mousemove', function (ev) {
@@ -245,7 +245,7 @@ export default class LineChart extends Component {
});
canvas.on('mouseleave', () => {
run.schedule('afterRender', this, () => (this.isActive = false));
schedule('afterRender', this, () => (this.isActive = false));
this.activeDatum = null;
this.activeData = [];
});
@@ -338,7 +338,7 @@ export default class LineChart extends Component {
// svg elements
this.mountD3Elements();
run.next(() => {
next(() => {
// Since each axis depends on the dimension of the other
// axis, the axes themselves are recomputed and need to
// be re-rendered.

View File

@@ -1,10 +1,15 @@
import Component from '@ember/component';
import { classNames, classNameBindings } from '@ember-decorators/component';
import {
classNames,
classNameBindings,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@classNames('accordion-head')
@classNameBindings('isOpen::is-light', 'isExpandable::is-inactive')
@attributeBindings('data-test-accordion-head')
export default class AccordionHead extends Component {
'data-test-accordion-head' = true;

View File

@@ -11,7 +11,7 @@ export default class ListTable extends Component {
@overridable(() => []) source;
// Plan for a future with metadata (e.g., isSelected)
@computed('source.[]')
@computed('source.{[],isFulfilled}')
get decoratedSource() {
return (this.source || []).map((row) => ({
model: row,

View File

@@ -1,7 +1,7 @@
import Component from '@ember/component';
import { action } from '@ember/object';
import { computed as overridable } from 'ember-overridable-computed';
import { run } from '@ember/runloop';
import { scheduleOnce } from '@ember/runloop';
import { classNames } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@@ -33,7 +33,7 @@ export default class MultiSelectDropdown extends Component {
super.didReceiveAttrs();
const dropdown = this.dropdown;
if (this.isOpen && dropdown) {
run.scheduleOnce('afterRender', this, this.repositionDropdown);
scheduleOnce('afterRender', this, this.repositionDropdown);
}
}

View File

@@ -1,7 +1,12 @@
import AllocationRow from 'nomad-ui/components/allocation-row';
import classic from 'ember-classic-decorator';
import { attributeBindings } from '@ember-decorators/component';
@classic
@attributeBindings(
'data-test-controller-allocation',
'data-test-node-allocation'
)
export default class PluginAllocationRow extends AllocationRow {
pluginAllocation = null;
allocation = null;

View File

@@ -1,6 +1,6 @@
import Component from '@ember/component';
import { action } from '@ember/object';
import { run } from '@ember/runloop';
import { scheduleOnce } from '@ember/runloop';
import { classNames } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@@ -35,7 +35,7 @@ export default class PopoverMenu extends Component {
super.didReceiveAttrs();
const dropdown = this.dropdown;
if (this.isOpen && dropdown) {
run.scheduleOnce('afterRender', this, this.repositionDropdown);
scheduleOnce('afterRender', this, this.repositionDropdown);
}
}

View File

@@ -1,8 +1,12 @@
import Component from '@ember/component';
import { run } from '@ember/runloop';
import { scheduleOnce, once } from '@ember/runloop';
import { task } from 'ember-concurrency';
import WindowResizable from 'nomad-ui/mixins/window-resizable';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
const A_KEY = 65;
@@ -10,6 +14,7 @@ const A_KEY = 65;
@classic
@tagName('pre')
@classNames('cli-window')
@attributeBindings('data-test-log-cli')
export default class StreamingFile extends Component.extend(WindowResizable) {
'data-test-log-cli' = true;
@@ -27,7 +32,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
return;
}
run.scheduleOnce('actions', this, this.performTask);
scheduleOnce('actions', this, this.performTask);
}
performTask() {
@@ -100,7 +105,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
}
windowResizeHandler() {
run.once(this, this.fillAvailableHeight);
once(this, this.fillAvailableHeight);
}
fillAvailableHeight() {
@@ -115,7 +120,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
@task(function* () {
yield this.get('logger.gotoHead').perform();
run.scheduleOnce('afterRender', this, this.scrollToTop);
scheduleOnce('afterRender', this, this.scrollToTop);
})
head;
@@ -144,7 +149,7 @@ export default class StreamingFile extends Component.extend(WindowResizable) {
stream;
scheduleScrollSynchronization() {
run.scheduleOnce('afterRender', this, this.synchronizeScrollPosition);
scheduleOnce('afterRender', this, this.synchronizeScrollPosition);
}
willDestroy() {

View File

@@ -5,12 +5,17 @@ import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import { task, timeout } from 'ember-concurrency';
import { lazyClick } from '../helpers/lazy-click';
import { classNames, tagName } from '@ember-decorators/component';
import {
classNames,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@classic
@tagName('tr')
@classNames('task-row', 'is-interactive')
@attributeBindings('data-test-task-row')
export default class TaskRow extends Component {
@service store;
@service token;

View File

@@ -3,6 +3,7 @@ import {
classNames,
classNameBindings,
tagName,
attributeBindings,
} from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@@ -10,6 +11,7 @@ import classic from 'ember-classic-decorator';
@tagName('label')
@classNames('toggle')
@classNameBindings('isDisabled:is-disabled', 'isActive:is-active')
@attributeBindings('data-test-label')
export default class Toggle extends Component {
'data-test-label' = true;

View File

@@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action, set } from '@ember/object';
import { inject as service } from '@ember/service';
import { run } from '@ember/runloop';
import { next } from '@ember/runloop';
import { scaleLinear } from 'd3-scale';
import { extent, deviation, mean } from 'd3-array';
import { line, curveBasis } from 'd3-shape';
@@ -268,7 +268,7 @@ export default class TopoViz extends Component {
@action
computedActiveEdges() {
// Wait a render cycle
run.next(() => {
next(() => {
const path = line().curve(curveBasis);
// 1. Get the active element
const allocation = this.activeAllocation.allocation;

View File

@@ -2,6 +2,7 @@ import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
import { schedule } from '@ember/runloop';
const noOp = () => undefined;
@@ -63,6 +64,8 @@ export default class Trigger extends Component {
@action
onTrigger() {
schedule('actions', () => {
this.triggerTask.perform();
});
}
}

View File

@@ -15,6 +15,7 @@ import classic from 'ember-classic-decorator';
@classic
export default class IndexController extends Controller.extend(Sortable) {
@service token;
@service store;
queryParams = [
{

View File

@@ -1,7 +1,7 @@
/* eslint-disable ember/no-observers */
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import { run } from '@ember/runloop';
import { next } from '@ember/runloop';
import { observes } from '@ember-decorators/object';
import { computed } from '@ember/object';
import Ember from 'ember';
@@ -14,6 +14,7 @@ import classic from 'ember-classic-decorator';
export default class ApplicationController extends Controller {
@service config;
@service system;
@service token;
queryParams = [
{
@@ -70,11 +71,11 @@ export default class ApplicationController extends Controller {
@observes('error')
throwError() {
if (this.get('config.isDev')) {
run.next(() => {
next(() => {
throw this.error;
});
} else if (!Ember.testing) {
run.next(() => {
next(() => {
// eslint-disable-next-line
console.warn('UNRECOVERABLE ERROR:', this.error);
});

View File

@@ -13,6 +13,7 @@ import {
deserializedQueryParam as selection,
} from 'nomad-ui/utils/qp-serialize';
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
@classic
export default class ClientsController extends Controller.extend(
@@ -20,6 +21,8 @@ export default class ClientsController extends Controller.extend(
Searchable,
WithNamespaceResetting
) {
@service store;
queryParams = [
{
currentPage: 'page',

View File

@@ -1,5 +1,5 @@
import Mixin from '@ember/object/mixin';
import { run } from '@ember/runloop';
import { scheduleOnce } from '@ember/runloop';
import { assert } from '@ember/debug';
import { on } from '@ember/object/evented';
@@ -13,7 +13,7 @@ export default Mixin.create({
},
setupWindowResize: on('didInsertElement', function () {
run.scheduleOnce('afterRender', this, this.addResizeListener);
scheduleOnce('afterRender', this, this.addResizeListener);
}),
addResizeListener() {

View File

@@ -1,4 +1,4 @@
import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
import Fragment from 'ember-data-model-fragments/fragment';
import { attr } from '@ember-data/model';
import {
@@ -19,6 +19,6 @@ export default class TaskGroupScale extends Fragment {
@fragmentArray('scale-event') events;
@computed.reads('events.length')
@reads('events.length')
isVisible;
}

View File

@@ -41,7 +41,7 @@ export default class TaskGroup extends Fragment {
return this.tasks.mapBy('driver').uniq();
}
@computed('job.allocations.@each.taskGroup', 'name')
@computed('job.allocations.{@each.taskGroup,isFulfilled}', 'name')
get allocations() {
return maybe(this.get('job.allocations')).filterBy(
'taskGroupName',

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchAll } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class IndexRoute extends Route.extend(WithWatchers) {
@service store;
startWatchers(controller) {
controller.set('watcher', this.watch.perform());
}

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchQuery } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class IndexRoute extends Route.extend(WithWatchers) {
@service store;
startWatchers(controller) {
controller.set('modelWatch', this.watch.perform({ type: 'csi' }));
}

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRecord } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class AllocationsRoute extends Route.extend(WithWatchers) {
@service store;
startWatchers(controller, model) {
if (!model) return;

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRecord } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class IndexRoute extends Route.extend(WithWatchers) {
@service store;
startWatchers(controller, model) {
if (!model) return;

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRelationship } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class AllocationsRoute extends Route.extend(WithWatchers) {
@service store;
model() {
const job = this.modelFor('jobs.job');
return job && job.get('allocations').then(() => job);

View File

@@ -10,6 +10,7 @@ import { collect } from '@ember/object/computed';
export default class ClientsRoute extends Route.extend(WithWatchers) {
@service can;
@service store;
beforeModel() {
if (this.can.cannot('read client')) {

View File

@@ -3,8 +3,11 @@ import RSVP from 'rsvp';
import { collect } from '@ember/object/computed';
import { watchRelationship } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class DeploymentsRoute extends Route.extend(WithWatchers) {
@service store;
model() {
const job = this.modelFor('jobs.job');
return (

View File

@@ -2,8 +2,11 @@ import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRelationship } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class EvaluationsRoute extends Route.extend(WithWatchers) {
@service store;
model() {
const job = this.modelFor('jobs.job');
return job && job.get('evaluations').then(() => job);

View File

@@ -11,6 +11,7 @@ import WithWatchers from 'nomad-ui/mixins/with-watchers';
export default class IndexRoute extends Route.extend(WithWatchers) {
@service can;
@service store;
async model() {
return this.modelFor('jobs.job');

View File

@@ -8,8 +8,11 @@ import {
} from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import notifyError from 'nomad-ui/utils/notify-error';
import { inject as service } from '@ember/service';
export default class TaskGroupRoute extends Route.extend(WithWatchers) {
@service store;
model({ name }) {
const job = this.modelFor('jobs.job');

View File

@@ -5,8 +5,11 @@ import {
watchRelationship,
} from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
import { inject as service } from '@ember/service';
export default class VersionsRoute extends Route.extend(WithWatchers) {
@service store;
model() {
const job = this.modelFor('jobs.job');
return job && job.get('versions').then(() => job);

View File

@@ -8,6 +8,7 @@ import RSVP from 'rsvp';
@classic
export default class OptimizeRoute extends Route {
@service can;
@service store;
beforeModel() {
if (this.can.cannot('accept recommendation')) {

View File

@@ -1,6 +1,8 @@
import ApplicationSerializer from './application';
import AdapterError from '@ember-data/adapter/error';
import classic from 'ember-classic-decorator';
@classic
export default class AgentSerializer extends ApplicationSerializer {
attrs = {
datacenter: 'dc',

View File

@@ -6,7 +6,9 @@ import JSONSerializer from '@ember-data/serializer/json';
import { pluralize, singularize } from 'ember-inflector';
import removeRecord from '../utils/remove-record';
import { assign } from '@ember/polyfills';
import classic from 'ember-classic-decorator';
@classic
export default class Application extends JSONSerializer {
primaryKey = 'ID';

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class DrainStrategy extends ApplicationSerializer {
normalize(typeHash, hash) {
// TODO API: finishedAt is always marshaled as a date even when unset.

View File

@@ -1,3 +1,5 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class Fragment extends ApplicationSerializer {}

View File

@@ -1,6 +1,8 @@
import ApplicationSerializer from './application';
import { get } from '@ember/object';
import classic from 'ember-classic-decorator';
@classic
export default class JobPlan extends ApplicationSerializer {
mapToArray = ['FailedTGAllocs'];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class JobScale extends ApplicationSerializer {
mapToArray = [{ beforeName: 'TaskGroups', afterName: 'TaskGroupScales' }];

View File

@@ -1,6 +1,8 @@
import { get } from '@ember/object';
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class JobSummary extends ApplicationSerializer {
normalize(modelClass, hash) {
hash.PlainJobId = hash.JobID;

View File

@@ -1,6 +1,8 @@
import { assign } from '@ember/polyfills';
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class JobVersionSerializer extends ApplicationSerializer {
attrs = {
number: 'Version',

View File

@@ -1,7 +1,9 @@
import { assign } from '@ember/polyfills';
import ApplicationSerializer from './application';
import queryString from 'query-string';
import classic from 'ember-classic-decorator';
@classic
export default class JobSerializer extends ApplicationSerializer {
attrs = {
parameterized: 'ParameterizedJob',

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class Namespace extends ApplicationSerializer {
primaryKey = 'Name';
}

View File

@@ -1,6 +1,8 @@
import ApplicationSerializer from './application';
import isIp from 'is-ip';
import classic from 'ember-classic-decorator';
@classic
export default class NetworkSerializer extends ApplicationSerializer {
attrs = {
cidr: 'CIDR',

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class NodeEventSerializer extends ApplicationSerializer {
attrs = {
time: 'Timestamp',

View File

@@ -1,4 +1,5 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
// Convert a map[string]interface{} into an array of objects
// where the key becomes a property at propKey.
@@ -14,6 +15,7 @@ const unmap = (hash, propKey) =>
return record;
});
@classic
export default class Plugin extends ApplicationSerializer {
normalize(typeHash, hash) {
hash.PlainId = hash.ID;

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class Policy extends ApplicationSerializer {
normalize(typeHash, hash) {
hash.ID = hash.Name;

View File

@@ -1,6 +1,8 @@
import ApplicationSerializer from './application';
import isIp from 'is-ip';
import classic from 'ember-classic-decorator';
@classic
export default class PortSerializer extends ApplicationSerializer {
attrs = {
hostIp: 'HostIP',

View File

@@ -1,5 +1,8 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class RescheduleEvent extends ApplicationSerializer {
separateNanos = ['Time'];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class ResourcesSerializer extends ApplicationSerializer {
arrayNullOverrides = ['Ports', 'Networks'];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class ScaleEventSerializer extends ApplicationSerializer {
separateNanos = ['Time'];
objectNullOverrides = ['Meta'];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class ServiceSerializer extends ApplicationSerializer {
attrs = {
connect: 'Connect',

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class StructuredAttributes extends ApplicationSerializer {
normalize(typeHash, hash) {
return super.normalize(typeHash, { Raw: hash });

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TaskEventSerializer extends ApplicationSerializer {
attrs = {
message: 'DisplayMessage',

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TaskGroupDeploymentSummary extends ApplicationSerializer {
normalize(typeHash, hash) {
hash.PlacedCanaryAllocations = hash.PlacedCanaries || [];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TaskGroupScaleSerializer extends ApplicationSerializer {
arrayNullOverrides = ['Events'];
}

View File

@@ -1,6 +1,8 @@
import { copy } from 'ember-copy';
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TaskGroup extends ApplicationSerializer {
arrayNullOverrides = ['Services'];
mapToArray = ['Volumes'];

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TaskState extends ApplicationSerializer {
normalize(typeHash, hash) {
// TODO API: finishedAt is always marshaled as a date even when unset.

View File

@@ -1,5 +1,7 @@
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class Task extends ApplicationSerializer {
normalize(typeHash, hash) {
// Lift the reserved resource numbers out of the Resources object

View File

@@ -1,6 +1,8 @@
import { copy } from 'ember-copy';
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class TokenSerializer extends ApplicationSerializer {
primaryKey = 'AccessorID';

View File

@@ -1,6 +1,8 @@
import { set, get } from '@ember/object';
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class VolumeSerializer extends ApplicationSerializer {
attrs = {
externalId: 'ExternalID',

View File

@@ -1,14 +1,18 @@
{{page-title (if this.system.shouldShowRegions (concat this.system.activeRegion " - ")) "Nomad" separator=" - "}}
{{page-title
(if this.system.shouldShowRegions (concat this.system.activeRegion " - "))
"Nomad"
separator=" - "
}}
<SvgPatterns />
{{#unless this.error}}
{{outlet}}
{{else}}
{{#if this.error}}
<div class="error-container">
<div data-test-error class="error-message">
{{#if this.isNoLeader}}
<h1 data-test-error-title class="title is-spaced">No Cluster Leader</h1>
<p data-test-error-message class="subtitle">
The cluster has no leader. <a href="https://www.nomadproject.io/guides/outage.html"> Read about Outage Recovery.</a>
The cluster has no leader.
<a href="https://www.nomadproject.io/guides/outage.html">
Read about Outage Recovery.</a>
</p>
{{else if this.isOTTExchange}}
<h1 data-test-error-title class="title is-spaced">Token Exchange Error</h1>
@@ -17,16 +21,24 @@
</p>
{{else if this.is500}}
<h1 data-test-error-title class="title is-spaced">Server Error</h1>
<p data-test-error-message class="subtitle">A server error prevented data from being sent to the client.</p>
<p data-test-error-message class="subtitle">A server error prevented
data from being sent to the client.</p>
{{else if this.is404}}
<h1 data-test-error-title class="title is-spaced">Not Found</h1>
<p data-test-error-message class="subtitle">What you're looking for couldn't be found. It either doesn't exist or you are not authorized to see it.</p>
<p data-test-error-message class="subtitle">What you're looking for
couldn't be found. It either doesn't exist or you are not authorized
to see it.</p>
{{else if this.is403}}
<h1 data-test-error-title class="title is-spaced">Not Authorized</h1>
{{#if this.token.secret}}
<p data-test-error-message class="subtitle">Your <LinkTo @route="settings.tokens" data-test-error-acl-link>ACL token</LinkTo> does not provide the required permissions. Contact your administrator if this is an error.</p>
<p data-test-error-message class="subtitle">Your
<LinkTo @route="settings.tokens" data-test-error-acl-link>ACL token</LinkTo>
does not provide the required permissions. Contact your
administrator if this is an error.</p>
{{else}}
<p data-test-error-message class="subtitle">Provide an <LinkTo @route="settings.tokens" data-test-error-acl-link>ACL token</LinkTo> with requisite permissions to view this.</p>
<p data-test-error-message class="subtitle">Provide an
<LinkTo @route="settings.tokens" data-test-error-acl-link>ACL token</LinkTo>
with requisite permissions to view this.</p>
{{/if}}
{{else}}
<h1 data-test-error-title class="title is-spaced">Error</h1>
@@ -37,8 +49,15 @@
{{/if}}
</div>
<div class="error-links">
<LinkTo @route="jobs" data-test-error-jobs-link class="button is-white">Go to Jobs</LinkTo>
<LinkTo @route="clients" data-test-error-clients-link class="button is-white">Go to Clients</LinkTo>
<LinkTo @route="jobs" data-test-error-jobs-link class="button is-white">Go
to Jobs</LinkTo>
<LinkTo
@route="clients"
data-test-error-clients-link
class="button is-white"
>Go to Clients</LinkTo>
</div>
</div>
{{/unless}}
{{else}}
{{outlet}}
{{/if}}

View File

@@ -9,7 +9,8 @@
<SearchBox
@searchTerm={{mut this.searchTerm}}
@onChange={{action this.resetPagination}}
@placeholder="Search clients..." />
@placeholder="Search clients..."
/>
{{/if}}
</div>
<div class="toolbar-item is-right-aligned is-mobile-full-width">
@@ -19,31 +20,36 @@
@label="Class"
@options={{this.optionsClass}}
@selection={{this.selectionClass}}
@onSelect={{action this.setFacetQueryParam "qpClass"}} />
@onSelect={{action this.setFacetQueryParam "qpClass"}}
/>
<MultiSelectDropdown
data-test-state-facet
@label="State"
@options={{this.optionsState}}
@selection={{this.selectionState}}
@onSelect={{action this.setFacetQueryParam "qpState"}} />
@onSelect={{action this.setFacetQueryParam "qpState"}}
/>
<MultiSelectDropdown
data-test-datacenter-facet
@label="Datacenter"
@options={{this.optionsDatacenter}}
@selection={{this.selectionDatacenter}}
@onSelect={{action this.setFacetQueryParam "qpDatacenter"}} />
@onSelect={{action this.setFacetQueryParam "qpDatacenter"}}
/>
<MultiSelectDropdown
data-test-version-facet
@label="Version"
@options={{this.optionsVersion}}
@selection={{this.selectionVersion}}
@onSelect={{action this.setFacetQueryParam "qpVersion"}} />
@onSelect={{action this.setFacetQueryParam "qpVersion"}}
/>
<MultiSelectDropdown
data-test-volume-facet
@label="Volume"
@options={{this.optionsVolume}}
@selection={{this.selectionVolume}}
@onSelect={{action this.setFacetQueryParam "qpVolume"}} />
@onSelect={{action this.setFacetQueryParam "qpVolume"}}
/>
</div>
</div>
</div>
@@ -51,16 +57,23 @@
<ListPagination
@source={{this.sortedNodes}}
@size={{this.pageSize}}
@page={{this.currentPage}} as |p|>
@page={{this.currentPage}}
as |p|
>
<ListTable
@source={{p.list}}
@sortProperty={{this.sortProperty}}
@sortDescending={{this.sortDescending}}
@class="with-foot" as |t|>
@class="with-foot"
as |t|
>
<t.head>
<th class="is-narrow"></th>
<t.sort-by @prop="id">ID</t.sort-by>
<t.sort-by @class="is-200px is-truncatable" @prop="name">Name</t.sort-by>
<t.sort-by
@class="is-200px is-truncatable"
@prop="name"
>Name</t.sort-by>
<t.sort-by @prop="compositeStatus">State</t.sort-by>
<th class="is-200px is-truncatable">Address</th>
<t.sort-by @prop="datacenter">Datacenter</t.sort-by>
@@ -69,16 +82,24 @@
<th># Allocs</th>
</t.head>
<t.body as |row|>
<ClientNodeRow data-test-client-node-row @node={{row.model}} @onClick={{action "gotoNode" row.model}} />
<ClientNodeRow
data-test-client-node-row
@node={{row.model}}
@onClick={{action "gotoNode" row.model}}
/>
</t.body>
</ListTable>
<div class="table-foot">
<PageSizeSelect @onChange={{action this.resetPagination}} />
<nav class="pagination" data-test-pagination>
<div class="pagination-numbers">
{{p.startsAt}}&ndash;{{p.endsAt}} of {{this.sortedNodes.length}}
{{p.startsAt}}&ndash;{{p.endsAt}}
of
{{this.sortedNodes.length}}
</div>
<p.prev @class="pagination-previous">{{x-icon "chevron-left"}}</p.prev>
<p.prev @class="pagination-previous">{{x-icon
"chevron-left"
}}</p.prev>
<p.next @class="pagination-next">{{x-icon "chevron-right"}}</p.next>
<ul class="pagination-list"></ul>
</nav>
@@ -87,18 +108,28 @@
{{else}}
<div class="empty-message" data-test-empty-clients-list>
{{#if (eq this.nodes.length 0)}}
<h3 class="empty-message-headline" data-test-empty-clients-list-headline>No Clients</h3>
<h3
class="empty-message-headline"
data-test-empty-clients-list-headline
>No Clients</h3>
<p class="empty-message-body">
The cluster currently has no client nodes.
</p>
{{else if (eq this.filteredNodes.length 0)}}
<h3 data-test-empty-clients-list-headline class="empty-message-headline">No Matches</h3>
<h3
data-test-empty-clients-list-headline
class="empty-message-headline"
>No Matches</h3>
<p class="empty-message-body">
No clients match your current filter selection.
</p>
{{else if this.searchTerm}}
<h3 class="empty-message-headline" data-test-empty-clients-list-headline>No Matches</h3>
<p class="empty-message-body">No clients match the term <strong>{{this.searchTerm}}</strong></p>
<h3
class="empty-message-headline"
data-test-empty-clients-list-headline
>No Matches</h3>
<p class="empty-message-body">No clients match the term
<strong>{{this.searchTerm}}</strong></p>
{{/if}}
</div>
{{/if}}

Some files were not shown because too many files have changed in this diff Show More