diff --git a/ui/app/adapters/allocation.js b/ui/app/adapters/allocation.js new file mode 100644 index 00000000000..8aa4b662fd5 --- /dev/null +++ b/ui/app/adapters/allocation.js @@ -0,0 +1,3 @@ +import Watchable from './watchable'; + +export default Watchable.extend(); diff --git a/ui/app/adapters/node.js b/ui/app/adapters/node.js index 349e44b821a..5d77bebd268 100644 --- a/ui/app/adapters/node.js +++ b/ui/app/adapters/node.js @@ -1,6 +1,6 @@ -import ApplicationAdapter from './application'; +import Watchable from './watchable'; -export default ApplicationAdapter.extend({ +export default Watchable.extend({ findAllocations(node) { const url = `${this.buildURL('node', node.get('id'), node, 'findRecord')}/allocations`; return this.ajax(url, 'GET').then(allocs => { diff --git a/ui/app/adapters/watchable.js b/ui/app/adapters/watchable.js index 857b5f87ee7..5b71a5e3f09 100644 --- a/ui/app/adapters/watchable.js +++ b/ui/app/adapters/watchable.js @@ -1,6 +1,5 @@ import { get, computed } from '@ember/object'; import { assign } from '@ember/polyfills'; -import { makeArray } from '@ember/array'; import { inject as service } from '@ember/service'; import queryString from 'npm:query-string'; import ApplicationAdapter from './application'; @@ -37,15 +36,11 @@ export default ApplicationAdapter.extend({ if (get(snapshotRecordArray || {}, 'adapterOptions.watch')) { params.index = this.get('watchList').getIndexFor(url); + this.cancelFindAll(type.modelName); } return this.ajax(url, 'GET', { data: params, - }).catch(error => { - if (error instanceof AbortError) { - return []; - } - throw error; }); }, @@ -55,6 +50,7 @@ export default ApplicationAdapter.extend({ if (get(snapshot || {}, 'adapterOptions.watch')) { params.index = this.get('watchList').getIndexFor(url); + this.cancelFindRecord(type.modelName, id); } return this.ajax(url, 'GET', { @@ -79,6 +75,7 @@ export default ApplicationAdapter.extend({ if (watch) { params.index = this.get('watchList').getIndexFor(url); + this.cancelReloadRelationship(model, relationshipName); } if (url.includes('?')) { @@ -89,9 +86,15 @@ export default ApplicationAdapter.extend({ data: params, }).then( json => { - this.get('store').pushPayload(relationship.type, { - [relationship.type]: makeArray(json), - }); + const store = this.get('store'); + const normalizeMethod = + relationship.kind === 'belongsTo' + ? 'normalizeFindBelongsToResponse' + : 'normalizeFindHasManyResponse'; + const serializer = store.serializerFor(relationship.type); + const modelClass = store.modelFor(relationship.type); + const normalizedData = serializer[normalizeMethod](store, modelClass, json); + store.push(normalizedData); }, error => { if (error instanceof AbortError) { diff --git a/ui/app/components/client-node-row.js b/ui/app/components/client-node-row.js index 2775ed58fbb..7d9d7f8289a 100644 --- a/ui/app/components/client-node-row.js +++ b/ui/app/components/client-node-row.js @@ -1,7 +1,11 @@ +import { inject as service } from '@ember/service'; import Component from '@ember/component'; import { lazyClick } from '../helpers/lazy-click'; +import { watchRelationship } from 'nomad-ui/utils/properties/watch'; export default Component.extend({ + store: service(), + tagName: 'tr', classNames: ['client-node-row', 'is-interactive'], @@ -17,7 +21,16 @@ export default Component.extend({ // Reload the node in order to get detail information const node = this.get('node'); if (node) { - node.reload(); + node.reload().then(() => { + this.get('watch').perform(node, 100); + }); } }, + + willDestroy() { + this.get('watch').cancelAll(); + this._super(...arguments); + }, + + watch: watchRelationship('allocations'), }); diff --git a/ui/app/components/job-versions-stream.js b/ui/app/components/job-versions-stream.js index b2a92d71a8b..460d0b9a244 100644 --- a/ui/app/components/job-versions-stream.js +++ b/ui/app/components/job-versions-stream.js @@ -12,7 +12,9 @@ export default Component.extend({ verbose: true, annotatedVersions: computed('versions.[]', function() { - const versions = this.get('versions'); + const versions = this.get('versions') + .sortBy('submitTime') + .reverse(); return versions.map((version, index) => { const meta = {}; diff --git a/ui/app/mixins/with-watchers.js b/ui/app/mixins/with-watchers.js new file mode 100644 index 00000000000..c486e01ff80 --- /dev/null +++ b/ui/app/mixins/with-watchers.js @@ -0,0 +1,16 @@ +import Mixin from '@ember/object/mixin'; +import { computed } from '@ember/object'; +import { assert } from '@ember/debug'; + +export default Mixin.create({ + watchers: computed(() => []), + + actions: { + willTransition() { + this.get('watchers').forEach(watcher => { + assert('Watchers must be Ember Concurrency Tasks.', !!watcher.cancelAll); + watcher.cancelAll(); + }); + }, + }, +}); diff --git a/ui/app/routes/allocations/allocation.js b/ui/app/routes/allocations/allocation.js index 17aa8b10c92..6f6d8ebaea2 100644 --- a/ui/app/routes/allocations/allocation.js +++ b/ui/app/routes/allocations/allocation.js @@ -1,4 +1,16 @@ import Route from '@ember/routing/route'; import WithModelErrorHandling from 'nomad-ui/mixins/with-model-error-handling'; +import { collect } from '@ember/object/computed'; +import { watchRecord } from 'nomad-ui/utils/properties/watch'; +import WithWatchers from 'nomad-ui/mixins/with-watchers'; -export default Route.extend(WithModelErrorHandling); +export default Route.extend(WithModelErrorHandling, WithWatchers, { + setupController(controller, model) { + controller.set('watcher', this.get('watch').perform(model)); + return this._super(...arguments); + }, + + watch: watchRecord('allocation'), + + watchers: collect('watch'), +}); diff --git a/ui/app/routes/clients/client.js b/ui/app/routes/clients/client.js index 1be621e4749..25c7ee8616e 100644 --- a/ui/app/routes/clients/client.js +++ b/ui/app/routes/clients/client.js @@ -1,8 +1,11 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; +import { collect } from '@ember/object/computed'; import notifyError from 'nomad-ui/utils/notify-error'; +import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import WithWatchers from 'nomad-ui/mixins/with-watchers'; -export default Route.extend({ +export default Route.extend(WithWatchers, { store: service(), model() { @@ -15,4 +18,15 @@ export default Route.extend({ } return model && model.get('allocations'); }, + + setupController(controller, model) { + controller.set('watchModel', this.get('watch').perform(model)); + controller.set('watchAllocations', this.get('watchAllocations').perform(model)); + return this._super(...arguments); + }, + + watch: watchRecord('node'), + watchAllocations: watchRelationship('allocations'), + + watchers: collect('watch', 'watchAllocations'), }); diff --git a/ui/app/routes/clients/index.js b/ui/app/routes/clients/index.js new file mode 100644 index 00000000000..d3493a3e685 --- /dev/null +++ b/ui/app/routes/clients/index.js @@ -0,0 +1,14 @@ +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'; + +export default Route.extend(WithWatchers, { + setupController(controller) { + controller.set('watcher', this.get('watch').perform()); + return this._super(...arguments); + }, + + watch: watchAll('node'), + watchers: collect('watch'), +}); diff --git a/ui/app/routes/jobs.js b/ui/app/routes/jobs.js index 181bcb1822f..745e326e2e2 100644 --- a/ui/app/routes/jobs.js +++ b/ui/app/routes/jobs.js @@ -3,7 +3,6 @@ import Route from '@ember/routing/route'; import { run } from '@ember/runloop'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; -import { watchAll } from 'nomad-ui/utils/properties/watch'; export default Route.extend(WithForbiddenState, { system: service(), @@ -36,18 +35,9 @@ export default Route.extend(WithForbiddenState, { setupController(controller) { this.syncToController(controller); - - controller.set('modelWatch', this.get('watch').perform()); return this._super(...arguments); }, - deactivate() { - this.get('watch').cancelAll(); - this._super(...arguments); - }, - - watch: watchAll('job'), - actions: { refreshRoute() { this.refresh(); diff --git a/ui/app/routes/jobs/index.js b/ui/app/routes/jobs/index.js index 0a8317feac4..1cfaef7267d 100644 --- a/ui/app/routes/jobs/index.js +++ b/ui/app/routes/jobs/index.js @@ -1,6 +1,17 @@ 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'; + +export default Route.extend(WithWatchers, { + setupController(controller) { + controller.set('modelWatch', this.get('watch').perform()); + return this._super(...arguments); + }, + + watch: watchAll('job'), + watchers: collect('watch'), -export default Route.extend({ actions: { refreshRoute() { return true; diff --git a/ui/app/routes/jobs/job.js b/ui/app/routes/jobs/job.js index 5e16030c5e3..86b784508ce 100644 --- a/ui/app/routes/jobs/job.js +++ b/ui/app/routes/jobs/job.js @@ -1,14 +1,11 @@ import { inject as service } from '@ember/service'; -import { collect } from '@ember/object/computed'; import Route from '@ember/routing/route'; import RSVP from 'rsvp'; import notifyError from 'nomad-ui/utils/notify-error'; -import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; export default Route.extend({ store: service(), token: service(), - watchList: service(), serialize(model) { return { job_name: model.get('plainId') }; @@ -25,29 +22,4 @@ export default Route.extend({ }) .catch(notifyError(this)); }, - - setupController(controller, model) { - controller.set('watchers', { - model: this.get('watch').perform(model), - summary: this.get('watchSummary').perform(model), - evaluations: this.get('watchEvaluations').perform(model), - deployments: this.get('watchDeployments').perform(model), - }); - - return this._super(...arguments); - }, - - deactivate() { - this.get('allWatchers').forEach(watcher => { - watcher.cancelAll(); - }); - this._super(...arguments); - }, - - watch: watchRecord('job'), - watchSummary: watchRelationship('summary'), - watchEvaluations: watchRelationship('evaluations'), - watchDeployments: watchRelationship('deployments'), - - allWatchers: collect('watch', 'watchSummary', 'watchEvaluations', 'watchDeployments'), }); diff --git a/ui/app/routes/jobs/job/deployments.js b/ui/app/routes/jobs/job/deployments.js index 41363eff69b..7bd29c31c87 100644 --- a/ui/app/routes/jobs/job/deployments.js +++ b/ui/app/routes/jobs/job/deployments.js @@ -1,9 +1,23 @@ import Route from '@ember/routing/route'; 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'; -export default Route.extend({ +export default Route.extend(WithWatchers, { model() { const job = this.modelFor('jobs.job'); return RSVP.all([job.get('deployments'), job.get('versions')]).then(() => job); }, + + setupController(controller, model) { + controller.set('watchDeployments', this.get('watchDeployments').perform(model)); + controller.set('watchVersions', this.get('watchVersions').perform(model)); + return this._super(...arguments); + }, + + watchDeployments: watchRelationship('deployments'), + watchVersions: watchRelationship('versions'), + + watchers: collect('watchDeployments', 'watchVersions'), }); diff --git a/ui/app/routes/jobs/job/index.js b/ui/app/routes/jobs/job/index.js new file mode 100644 index 00000000000..aa99a6eb78f --- /dev/null +++ b/ui/app/routes/jobs/job/index.js @@ -0,0 +1,24 @@ +import Route from '@ember/routing/route'; +import { collect } from '@ember/object/computed'; +import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import WithWatchers from 'nomad-ui/mixins/with-watchers'; + +export default Route.extend(WithWatchers, { + setupController(controller, model) { + controller.set('watchers', { + model: this.get('watch').perform(model), + summary: this.get('watchSummary').perform(model), + evaluations: this.get('watchEvaluations').perform(model), + deployments: this.get('watchDeployments').perform(model), + }); + + return this._super(...arguments); + }, + + watch: watchRecord('job'), + watchSummary: watchRelationship('summary'), + watchEvaluations: watchRelationship('evaluations'), + watchDeployments: watchRelationship('deployments'), + + watchers: collect('watch', 'watchSummary', 'watchEvaluations', 'watchDeployments'), +}); diff --git a/ui/app/routes/jobs/job/task-group.js b/ui/app/routes/jobs/job/task-group.js index def6e57eaac..53c14b67168 100644 --- a/ui/app/routes/jobs/job/task-group.js +++ b/ui/app/routes/jobs/job/task-group.js @@ -1,6 +1,9 @@ import Route from '@ember/routing/route'; +import { collect } from '@ember/object/computed'; +import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import WithWatchers from 'nomad-ui/mixins/with-watchers'; -export default Route.extend({ +export default Route.extend(WithWatchers, { model({ name }) { // If the job is a partial (from the list request) it won't have task // groups. Reload the job to ensure task groups are present. @@ -15,4 +18,20 @@ export default Route.extend({ }); }); }, + + setupController(controller, model) { + const job = model.get('job'); + controller.set('watchers', { + job: this.get('watchJob').perform(job), + summary: this.get('watchSummary').perform(job), + allocations: this.get('watchAllocations').perform(job), + }); + return this._super(...arguments); + }, + + watchJob: watchRecord('job'), + watchSummary: watchRelationship('summary'), + watchAllocations: watchRelationship('allocations'), + + watchers: collect('watchJob', 'watchSummary', 'watchAllocations'), }); diff --git a/ui/app/routes/jobs/job/versions.js b/ui/app/routes/jobs/job/versions.js index 6debc85db39..154476990fe 100644 --- a/ui/app/routes/jobs/job/versions.js +++ b/ui/app/routes/jobs/job/versions.js @@ -1,8 +1,19 @@ 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'; -export default Route.extend({ +export default Route.extend(WithWatchers, { model() { const job = this.modelFor('jobs.job'); return job.get('versions').then(() => job); }, + + setupController(controller, model) { + controller.set('watcher', this.get('watchVersions').perform(model)); + return this._super(...arguments); + }, + + watchVersions: watchRelationship('versions'), + watchers: collect('watchVersions'), }); diff --git a/ui/app/serializers/job-version.js b/ui/app/serializers/job-version.js index f05809b3f24..4e250d5d8d5 100644 --- a/ui/app/serializers/job-version.js +++ b/ui/app/serializers/job-version.js @@ -11,6 +11,7 @@ export default ApplicationSerializer.extend({ assign({}, version, { Diff: hash.Diffs && hash.Diffs[index], ID: `${version.ID}-${version.Version}`, + JobID: JSON.stringify([version.ID, version.Namespace || 'default']), SubmitTime: Math.floor(version.SubmitTime / 1000000), SubmitTimeNanos: version.SubmitTime % 1000000, }) diff --git a/ui/app/templates/components/job-deployments-stream.hbs b/ui/app/templates/components/job-deployments-stream.hbs index cf89df6150d..3479877873a 100644 --- a/ui/app/templates/components/job-deployments-stream.hbs +++ b/ui/app/templates/components/job-deployments-stream.hbs @@ -1,4 +1,4 @@ -{{#each annotatedDeployments as |record|}} +{{#each annotatedDeployments key="deployment.id" as |record|}} {{#if record.meta.showDate}}