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 5ae30849a9
commit e096a0a5ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
@ -21,10 +21,10 @@ $ yarn
## Running / Development
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.
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() {
this.triggerTask.perform();
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