diff --git a/ui/app/adapters/agent.js b/ui/app/adapters/agent.js index ab259e9ef..8b2d24fa8 100644 --- a/ui/app/adapters/agent.js +++ b/ui/app/adapters/agent.js @@ -1,6 +1,6 @@ import ApplicationAdapter from './application'; -export default class Agent extends ApplicationAdapter { +export default class AgentAdapter extends ApplicationAdapter { pathForType = () => 'agent/members'; urlForFindRecord() { diff --git a/ui/app/adapters/allocation.js b/ui/app/adapters/allocation.js index 93981bc57..20cca3c29 100644 --- a/ui/app/adapters/allocation.js +++ b/ui/app/adapters/allocation.js @@ -1,8 +1,8 @@ import Watchable from './watchable'; import addToPath from 'nomad-ui/utils/add-to-path'; -export default Watchable.extend({ - stop: adapterAction('/stop'), +export default class AllocationAdapter extends Watchable { + stop = adapterAction('/stop'); restart(allocation, taskName) { const prefix = `${this.host || '/'}${this.urlPrefix()}`; @@ -10,22 +10,20 @@ export default Watchable.extend({ return this.ajax(url, 'PUT', { data: taskName && { TaskName: taskName }, }); - }, + } ls(model, path) { return this.token .authorizedRequest(`/v1/client/fs/ls/${model.id}?path=${encodeURIComponent(path)}`) .then(handleFSResponse); - }, + } stat(model, path) { return this.token - .authorizedRequest( - `/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}` - ) + .authorizedRequest(`/v1/client/fs/stat/${model.id}?path=${encodeURIComponent(path)}`) .then(handleFSResponse); - }, -}); + } +} async function handleFSResponse(response) { if (response.ok) { diff --git a/ui/app/adapters/application.js b/ui/app/adapters/application.js index fcc23ed8f..e03cdffe3 100644 --- a/ui/app/adapters/application.js +++ b/ui/app/adapters/application.js @@ -9,7 +9,7 @@ import classic from 'ember-classic-decorator'; export const namespace = 'v1'; @classic -export default class Application extends RESTAdapter { +export default class ApplicationAdapter extends RESTAdapter { namespace = namespace; @service system; diff --git a/ui/app/adapters/deployment.js b/ui/app/adapters/deployment.js index b45f22e56..5530166d6 100644 --- a/ui/app/adapters/deployment.js +++ b/ui/app/adapters/deployment.js @@ -1,6 +1,6 @@ import Watchable from './watchable'; -export default class Deployment extends Watchable { +export default class DeploymentAdapter extends Watchable { promote(deployment) { const id = deployment.get('id'); const url = urlForAction(this.urlForFindRecord(id, 'deployment'), '/promote'); diff --git a/ui/app/adapters/job-summary.js b/ui/app/adapters/job-summary.js index 92a35bb5d..569311c66 100644 --- a/ui/app/adapters/job-summary.js +++ b/ui/app/adapters/job-summary.js @@ -1,6 +1,6 @@ import Watchable from './watchable'; -export default class JobSummary extends Watchable { +export default class JobSummaryAdapter extends Watchable { urlForFindRecord(id, type, hash) { const [name, namespace] = JSON.parse(id); let url = super.urlForFindRecord(name, 'job', hash) + '/summary'; diff --git a/ui/app/adapters/job.js b/ui/app/adapters/job.js index 10b0e1cb2..a052e5c8d 100644 --- a/ui/app/adapters/job.js +++ b/ui/app/adapters/job.js @@ -1,27 +1,27 @@ import WatchableNamespaceIDs from './watchable-namespace-ids'; import addToPath from 'nomad-ui/utils/add-to-path'; -export default WatchableNamespaceIDs.extend({ - relationshipFallbackLinks: Object.freeze({ +export default class JobAdapter extends WatchableNamespaceIDs { + relationshipFallbackLinks = Object.freeze({ summary: '/summary', - }), + }); fetchRawDefinition(job) { const url = this.urlForFindRecord(job.get('id'), 'job'); return this.ajax(url, 'GET'); - }, + } forcePeriodic(job) { if (job.get('periodic')) { const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/periodic/force'); return this.ajax(url, 'POST'); } - }, + } stop(job) { const url = this.urlForFindRecord(job.get('id'), 'job'); return this.ajax(url, 'DELETE'); - }, + } parse(spec) { const url = addToPath(this.urlForFindAll('job'), '/parse'); @@ -31,7 +31,7 @@ export default WatchableNamespaceIDs.extend({ Canonicalize: true, }, }); - }, + } plan(job) { const jobId = job.get('id') || job.get('_idBeforeSaving'); @@ -48,7 +48,7 @@ export default WatchableNamespaceIDs.extend({ store.pushPayload('job-plan', { jobPlans: [json] }); return store.peekRecord('job-plan', jobId); }); - }, + } // Running a job doesn't follow REST create semantics so it's easier to // treat it as an action. @@ -58,7 +58,7 @@ export default WatchableNamespaceIDs.extend({ Job: job.get('_newDefinitionJSON'), }, }); - }, + } update(job) { const jobId = job.get('id') || job.get('_idBeforeSaving'); @@ -68,5 +68,5 @@ export default WatchableNamespaceIDs.extend({ Job: job.get('_newDefinitionJSON'), }, }); - }, -}); + } +} diff --git a/ui/app/adapters/namespace.js b/ui/app/adapters/namespace.js index 89a004578..365c0933d 100644 --- a/ui/app/adapters/namespace.js +++ b/ui/app/adapters/namespace.js @@ -1,7 +1,7 @@ import ApplicationAdapter from './application'; import codesForError from '../utils/codes-for-error'; -export default class Namespace extends ApplicationAdapter { +export default class NamespaceAdapter extends ApplicationAdapter { findRecord(store, modelClass, id) { return super.findRecord(...arguments).catch(error => { const errorCodes = codesForError(error); diff --git a/ui/app/adapters/node.js b/ui/app/adapters/node.js index cde6ef79f..746f51f6e 100644 --- a/ui/app/adapters/node.js +++ b/ui/app/adapters/node.js @@ -1,7 +1,7 @@ import Watchable from './watchable'; import addToPath from 'nomad-ui/utils/add-to-path'; -export default class Node extends Watchable { +export default class NodeAdapter extends Watchable { setEligible(node) { return this.setEligibility(node, true); } diff --git a/ui/app/adapters/plugin.js b/ui/app/adapters/plugin.js index 836488ad1..ff304dbdf 100644 --- a/ui/app/adapters/plugin.js +++ b/ui/app/adapters/plugin.js @@ -1,7 +1,7 @@ import Watchable from './watchable'; -export default Watchable.extend({ - queryParamsToAttrs: Object.freeze({ +export default class PluginAdapter extends Watchable { + queryParamsToAttrs = Object.freeze({ type: 'type', - }), -}); + }); +} diff --git a/ui/app/adapters/policy.js b/ui/app/adapters/policy.js index e21e301e7..79e68bcc4 100644 --- a/ui/app/adapters/policy.js +++ b/ui/app/adapters/policy.js @@ -1,5 +1,5 @@ import { default as ApplicationAdapter, namespace } from './application'; -export default class Policy extends ApplicationAdapter { +export default class PolicyAdapter extends ApplicationAdapter { namespace = namespace + '/acl'; } diff --git a/ui/app/adapters/token.js b/ui/app/adapters/token.js index b0a149dc0..771d101d9 100644 --- a/ui/app/adapters/token.js +++ b/ui/app/adapters/token.js @@ -1,7 +1,7 @@ import { inject as service } from '@ember/service'; import { default as ApplicationAdapter, namespace } from './application'; -export default class Token extends ApplicationAdapter { +export default class TokenAdapter extends ApplicationAdapter { @service store; namespace = namespace + '/acl'; diff --git a/ui/app/adapters/volume.js b/ui/app/adapters/volume.js index b6076bf76..fbc665b32 100644 --- a/ui/app/adapters/volume.js +++ b/ui/app/adapters/volume.js @@ -1,8 +1,8 @@ import WatchableNamespaceIDs from './watchable-namespace-ids'; -export default WatchableNamespaceIDs.extend({ - queryParamsToAttrs: Object.freeze({ +export default class VolumeAdapter extends WatchableNamespaceIDs { + queryParamsToAttrs = Object.freeze({ type: 'type', plugin_id: 'plugin.id', - }), -}); + }); +} diff --git a/ui/app/components/allocation-status-bar.js b/ui/app/components/allocation-status-bar.js index 6e3046570..803884b6d 100644 --- a/ui/app/components/allocation-status-bar.js +++ b/ui/app/components/allocation-status-bar.js @@ -1,45 +1,45 @@ import { computed } from '@ember/object'; import DistributionBar from './distribution-bar'; -export default DistributionBar.extend({ - layoutName: 'components/distribution-bar', +export default class AllocationStatusBar extends DistributionBar { + layoutName = 'components/distribution-bar'; - allocationContainer: null, + allocationContainer = null; - 'data-test-allocation-status-bar': true, + 'data-test-allocation-status-bar' = true; - data: computed( - 'allocationContainer.{queuedAllocs,completeAllocs,failedAllocs,runningAllocs,startingAllocs}', - function() { - if (!this.allocationContainer) { - return []; - } - - const allocs = this.allocationContainer.getProperties( - 'queuedAllocs', - 'completeAllocs', - 'failedAllocs', - 'runningAllocs', - 'startingAllocs', - 'lostAllocs' - ); - return [ - { label: 'Queued', value: allocs.queuedAllocs, className: 'queued' }, - { - label: 'Starting', - value: allocs.startingAllocs, - className: 'starting', - layers: 2, - }, - { label: 'Running', value: allocs.runningAllocs, className: 'running' }, - { - label: 'Complete', - value: allocs.completeAllocs, - className: 'complete', - }, - { label: 'Failed', value: allocs.failedAllocs, className: 'failed' }, - { label: 'Lost', value: allocs.lostAllocs, className: 'lost' }, - ]; + @computed( + 'allocationContainer.{queuedAllocs,completeAllocs,failedAllocs,runningAllocs,startingAllocs}' + ) + get data() { + if (!this.allocationContainer) { + return []; } - ), -}); + + const allocs = this.allocationContainer.getProperties( + 'queuedAllocs', + 'completeAllocs', + 'failedAllocs', + 'runningAllocs', + 'startingAllocs', + 'lostAllocs' + ); + return [ + { label: 'Queued', value: allocs.queuedAllocs, className: 'queued' }, + { + label: 'Starting', + value: allocs.startingAllocs, + className: 'starting', + layers: 2, + }, + { label: 'Running', value: allocs.runningAllocs, className: 'running' }, + { + label: 'Complete', + value: allocs.completeAllocs, + className: 'complete', + }, + { label: 'Failed', value: allocs.failedAllocs, className: 'failed' }, + { label: 'Lost', value: allocs.lostAllocs, className: 'lost' }, + ]; + } +} diff --git a/ui/app/components/distribution-bar.js b/ui/app/components/distribution-bar.js index 743aead63..271fa6806 100644 --- a/ui/app/components/distribution-bar.js +++ b/ui/app/components/distribution-bar.js @@ -1,6 +1,7 @@ /* eslint-disable ember/no-observers */ import Component from '@ember/component'; -import { computed, observer, set } from '@ember/object'; +import { computed, set } from '@ember/object'; +import { observes } from '@ember-decorators/object'; import { run } from '@ember/runloop'; import { assign } from '@ember/polyfills'; import { guidFor } from '@ember/object/internals'; @@ -9,22 +10,25 @@ import d3 from 'd3-selection'; import 'd3-transition'; import WindowResizable from '../mixins/window-resizable'; import styleStringProperty from '../utils/properties/style-string'; +import { classNames, classNameBindings } from '@ember-decorators/component'; +import classic from 'ember-classic-decorator'; const sumAggregate = (total, val) => total + val; -export default Component.extend(WindowResizable, { - classNames: ['chart', 'distribution-bar'], - classNameBindings: ['isNarrow:is-narrow'], +@classic +@classNames('chart', 'distribution-bar') +@classNameBindings('isNarrow:is-narrow') +export default class DistributionBar extends Component.extend(WindowResizable) { + chart = null; + data = null; + activeDatum = null; + isNarrow = false; - chart: null, - data: null, - activeDatum: null, - isNarrow: false, + @styleStringProperty('tooltipPosition') tooltipStyle; + maskId = null; - tooltipStyle: styleStringProperty('tooltipPosition'), - maskId: null, - - _data: computed('data', function() { + @computed('data') + get _data() { const data = copy(this.data, true); const sum = data.mapBy('value').reduce(sumAggregate, 0); @@ -41,7 +45,7 @@ export default Component.extend(WindowResizable, { .mapBy('value') .reduce(sumAggregate, 0) / sum, })); - }), + } didInsertElement() { const svg = this.element.querySelector('svg'); @@ -63,15 +67,16 @@ export default Component.extend(WindowResizable, { }); this.renderChart(); - }, + } didUpdateAttrs() { this.renderChart(); - }, + } - updateChart: observer('_data.@each.{value,label,className}', function() { + @observes('_data.@each.{value,label,className}') + updateChart() { this.renderChart(); - }), + } // prettier-ignore /* eslint-disable */ @@ -166,10 +171,10 @@ export default Component.extend(WindowResizable, { .attr('height', '6px') .attr('y', '50%'); } - }, + } /* eslint-enable */ windowResizeHandler() { run.once(this, this.renderChart); - }, -}); + } +} diff --git a/ui/app/components/exec/task-group-parent.js b/ui/app/components/exec/task-group-parent.js index 2eced1676..f56b7ecb1 100644 --- a/ui/app/components/exec/task-group-parent.js +++ b/ui/app/components/exec/task-group-parent.js @@ -1,16 +1,19 @@ import Component from '@ember/component'; import { inject as service } from '@ember/service'; -import { computed } from '@ember/object'; +import { action, computed } from '@ember/object'; import { filterBy, mapBy, or, sort } from '@ember/object/computed'; import generateExecUrl from 'nomad-ui/utils/generate-exec-url'; import openExecUrl from 'nomad-ui/utils/open-exec-url'; +import classic from 'ember-classic-decorator'; -export default Component.extend({ - router: service(), +@classic +export default class TaskGroupParent extends Component { + @service router; - isOpen: or('clickedOpen', 'currentRouteIsThisTaskGroup'), + @or('clickedOpen', 'currentRouteIsThisTaskGroup') isOpen; - currentRouteIsThisTaskGroup: computed('router.currentRoute', function() { + @computed('router.currentRoute') + get currentRouteIsThisTaskGroup() { const route = this.router.currentRoute; if (route.name.includes('task-group')) { @@ -24,58 +27,60 @@ export default Component.extend({ } else { return false; } - }), + } - hasPendingAllocations: computed('taskGroup.allocations.@each.clientStatus', function() { + @computed('taskGroup.allocations.@each.clientStatus') + get hasPendingAllocations() { return this.taskGroup.allocations.any(allocation => allocation.clientStatus === 'pending'); - }), + } - allocationTaskStatesRecordArrays: mapBy('taskGroup.allocations', 'states'), - allocationTaskStates: computed('allocationTaskStatesRecordArrays.[]', function() { + @mapBy('taskGroup.allocations', 'states') allocationTaskStatesRecordArrays; + @computed('allocationTaskStatesRecordArrays.[]') + get allocationTaskStates() { const flattenRecordArrays = (accumulator, recordArray) => accumulator.concat(recordArray.toArray()); return this.allocationTaskStatesRecordArrays.reduce(flattenRecordArrays, []); - }), + } - activeTaskStates: filterBy('allocationTaskStates', 'isActive'), + @filterBy('allocationTaskStates', 'isActive') activeTaskStates; - activeTasks: mapBy('activeTaskStates', 'task'), - activeTaskGroups: mapBy('activeTasks', 'taskGroup'), + @mapBy('activeTaskStates', 'task') activeTasks; + @mapBy('activeTasks', 'taskGroup') activeTaskGroups; - tasksWithRunningStates: computed( + @computed( 'taskGroup.name', 'activeTaskStates.@each.name', 'activeTasks.@each.name', - 'activeTaskGroups.@each.name', - function() { - const activeTaskStateNames = this.activeTaskStates - .filter(taskState => { - return taskState.task && taskState.task.taskGroup.name === this.taskGroup.name; - }) - .mapBy('name'); + 'activeTaskGroups.@each.name' + ) + get tasksWithRunningStates() { + const activeTaskStateNames = this.activeTaskStates + .filter(taskState => { + return taskState.task && taskState.task.taskGroup.name === this.taskGroup.name; + }) + .mapBy('name'); - return this.taskGroup.tasks.filter(task => activeTaskStateNames.includes(task.name)); - } - ), + return this.taskGroup.tasks.filter(task => activeTaskStateNames.includes(task.name)); + } - taskSorting: Object.freeze(['name']), - sortedTasks: sort('tasksWithRunningStates', 'taskSorting'), + taskSorting = Object.freeze(['name']); + @sort('tasksWithRunningStates', 'taskSorting') sortedTasks; - clickedOpen: false, + clickedOpen = false; - actions: { - toggleOpen() { - this.toggleProperty('clickedOpen'); - }, + @action + toggleOpen() { + this.toggleProperty('clickedOpen'); + } - openInNewWindow(job, taskGroup, task) { - let url = generateExecUrl(this.router, { - job, - taskGroup, - task, - }); + @action + openInNewWindow(job, taskGroup, task) { + let url = generateExecUrl(this.router, { + job, + taskGroup, + task, + }); - openExecUrl(url); - }, - }, -}); + openExecUrl(url); + } +} diff --git a/ui/app/components/line-chart.js b/ui/app/components/line-chart.js index d0b282db6..2dd5e6cee 100644 --- a/ui/app/components/line-chart.js +++ b/ui/app/components/line-chart.js @@ -1,6 +1,7 @@ /* eslint-disable ember/no-observers */ import Component from '@ember/component'; -import { computed, observer } from '@ember/object'; +import { computed } from '@ember/object'; +import { observes } from '@ember-decorators/object'; import { computed as overridable } from 'ember-overridable-computed'; import { guidFor } from '@ember/object/internals'; import { run } from '@ember/runloop'; @@ -13,6 +14,8 @@ import d3Format from 'd3-format'; import d3TimeFormat from 'd3-time-format'; import WindowResizable from 'nomad-ui/mixins/window-resizable'; import styleStringProperty from 'nomad-ui/utils/properties/style-string'; +import { classNames } from '@ember-decorators/component'; +import classic from 'ember-classic-decorator'; // Returns a new array with the specified number of points linearly // distributed across the bounds @@ -28,68 +31,73 @@ const lerp = ([low, high], numPoints) => { // Round a number or an array of numbers const nice = val => (val instanceof Array ? val.map(nice) : Math.round(val)); -export default Component.extend(WindowResizable, { - classNames: ['chart', 'line-chart'], - +@classic +@classNames('chart', 'line-chart') +export default class LineChart extends Component.extend(WindowResizable) { // Public API - data: null, - xProp: null, - yProp: null, - timeseries: false, - chartClass: 'is-primary', + data = null; + xProp = null; + yProp = null; + timeseries = false; + chartClass = 'is-primary'; - title: 'Line Chart', - description: null, + title = 'Line Chart'; + description = null; // Private Properties - width: 0, - height: 0, + width = 0; + height = 0; - isActive: false, + isActive = false; - fillId: computed(function() { + @computed() + get fillId() { return `line-chart-fill-${guidFor(this)}`; - }), + } - maskId: computed(function() { + @computed() + get maskId() { return `line-chart-mask-${guidFor(this)}`; - }), + } - activeDatum: null, + activeDatum = null; - activeDatumLabel: computed('activeDatum', function() { + @computed('activeDatum') + get activeDatumLabel() { const datum = this.activeDatum; - if (!datum) return; + if (!datum) return undefined; const x = datum[this.xProp]; return this.xFormat(this.timeseries)(x); - }), + } - activeDatumValue: computed('activeDatum', function() { + @computed('activeDatum') + get activeDatumValue() { const datum = this.activeDatum; - if (!datum) return; + if (!datum) return undefined; const y = datum[this.yProp]; return this.yFormat()(y); - }), + } // Overridable functions that retrurn formatter functions xFormat(timeseries) { return timeseries ? d3TimeFormat.timeFormat('%b') : d3Format.format(','); - }, + } yFormat() { return d3Format.format(',.2~r'); - }, + } - tooltipPosition: null, - tooltipStyle: styleStringProperty('tooltipPosition'), + tooltipPosition = null; + @styleStringProperty('tooltipPosition') tooltipStyle; - xScale: computed('data.[]', 'xProp', 'timeseries', 'yAxisOffset', function() { + @computed('data.[]', 'xProp', 'timeseries', 'yAxisOffset') + get xScale() { const xProp = this.xProp; const scale = this.timeseries ? d3Scale.scaleTime() : d3Scale.scaleLinear(); const data = this.data; @@ -99,25 +107,28 @@ export default Component.extend(WindowResizable, { scale.rangeRound([10, this.yAxisOffset]).domain(domain); return scale; - }), + } - xRange: computed('data.[]', 'xFormat', 'xProp', 'timeseries', function() { + @computed('data.[]', 'xFormat', 'xProp', 'timeseries') + get xRange() { const { xProp, timeseries, data } = this; const range = d3Array.extent(data, d => d[xProp]); const formatter = this.xFormat(timeseries); return range.map(formatter); - }), + } - yRange: computed('data.[]', 'yFormat', 'yProp', function() { + @computed('data.[]', 'yFormat', 'yProp') + get yRange() { const yProp = this.yProp; const range = d3Array.extent(this.data, d => d[yProp]); const formatter = this.yFormat(); return range.map(formatter); - }), + } - yScale: computed('data.[]', 'yProp', 'xAxisOffset', function() { + @computed('data.[]', 'yProp', 'xAxisOffset') + get yScale() { const yProp = this.yProp; let max = d3Array.max(this.data, d => d[yProp]) || 1; if (max > 1) { @@ -128,9 +139,10 @@ export default Component.extend(WindowResizable, { .scaleLinear() .rangeRound([this.xAxisOffset, 10]) .domain([0, max]); - }), + } - xAxis: computed('xScale', function() { + @computed('xScale') + get xAxis() { const formatter = this.xFormat(this.timeseries); return d3Axis @@ -138,17 +150,19 @@ export default Component.extend(WindowResizable, { .scale(this.xScale) .ticks(5) .tickFormat(formatter); - }), + } - yTicks: computed('xAxisOffset', function() { + @computed('xAxisOffset') + get yTicks() { const height = this.xAxisOffset; const tickCount = Math.ceil(height / 120) * 2 + 1; const domain = this.yScale.domain(); const ticks = lerp(domain, tickCount); return domain[1] - domain[0] > 1 ? nice(ticks) : ticks; - }), + } - yAxis: computed('yScale', function() { + @computed('yScale') + get yAxis() { const formatter = this.yFormat(); return d3Axis @@ -156,9 +170,10 @@ export default Component.extend(WindowResizable, { .scale(this.yScale) .tickValues(this.yTicks) .tickFormat(formatter); - }), + } - yGridlines: computed('yScale', function() { + @computed('yScale') + get yGridlines() { // The first gridline overlaps the x-axis, so remove it const [, ...ticks] = this.yTicks; @@ -168,33 +183,38 @@ export default Component.extend(WindowResizable, { .tickValues(ticks) .tickSize(-this.yAxisOffset) .tickFormat(''); - }), + } - xAxisHeight: computed(function() { + @computed() + get xAxisHeight() { // Avoid divide by zero errors by always having a height if (!this.element) return 1; const axis = this.element.querySelector('.x-axis'); return axis && axis.getBBox().height; - }), + } - yAxisWidth: computed(function() { + @computed() + get yAxisWidth() { // Avoid divide by zero errors by always having a width if (!this.element) return 1; const axis = this.element.querySelector('.y-axis'); return axis && axis.getBBox().width; - }), + } - xAxisOffset: overridable('height', 'xAxisHeight', function() { + @overridable('height', 'xAxisHeight', function() { return this.height - this.xAxisHeight; - }), + }) + xAxisOffset; - yAxisOffset: computed('width', 'yAxisWidth', function() { + @computed('width', 'yAxisWidth') + get yAxisOffset() { return this.width - this.yAxisWidth; - }), + } - line: computed('data.[]', 'xScale', 'yScale', function() { + @computed('data.[]', 'xScale', 'yScale') + get line() { const { xScale, yScale, xProp, yProp } = this; const line = d3Shape @@ -204,9 +224,10 @@ export default Component.extend(WindowResizable, { .y(d => yScale(d[yProp])); return line(this.data); - }), + } - area: computed('data.[]', 'xScale', 'yScale', function() { + @computed('data.[]', 'xScale', 'yScale') + get area() { const { xScale, yScale, xProp, yProp } = this; const area = d3Shape @@ -217,7 +238,7 @@ export default Component.extend(WindowResizable, { .y1(d => yScale(d[yProp])); return area(this.data); - }), + } didInsertElement() { this.updateDimensions(); @@ -243,11 +264,11 @@ export default Component.extend(WindowResizable, { run.schedule('afterRender', this, () => this.set('isActive', false)); this.set('activeDatum', null); }); - }, + } didUpdateAttrs() { this.renderChart(); - }, + } updateActiveDatum(mouseX) { const { xScale, xProp, yScale, yProp, data } = this; @@ -278,11 +299,12 @@ export default Component.extend(WindowResizable, { left: xScale(datum[xProp]), top: yScale(datum[yProp]) - 10, }); - }, + } - updateChart: observer('data.[]', function() { + @observes('data.[]') + updateChart() { this.renderChart(); - }), + } // The renderChart method should only ever be responsible for runtime calculations // and appending d3 created elements to the DOM (such as axes). @@ -308,7 +330,7 @@ export default Component.extend(WindowResizable, { this.updateActiveDatum(this.latestMouseX); } }); - }, + } mountD3Elements() { if (!this.isDestroyed && !this.isDestroying) { @@ -316,11 +338,11 @@ export default Component.extend(WindowResizable, { d3.select(this.element.querySelector('.y-axis')).call(this.yAxis); d3.select(this.element.querySelector('.y-gridlines')).call(this.yGridlines); } - }, + } windowResizeHandler() { run.once(this, this.updateDimensions); - }, + } updateDimensions() { const $svg = this.element.querySelector('svg'); @@ -329,5 +351,5 @@ export default Component.extend(WindowResizable, { this.setProperties({ width, height }); this.renderChart(); - }, -}); + } +} diff --git a/ui/app/components/page-size-select.js b/ui/app/components/page-size-select.js index 0776d7084..3c6c3f0b3 100644 --- a/ui/app/components/page-size-select.js +++ b/ui/app/components/page-size-select.js @@ -1,11 +1,11 @@ import Component from '@ember/component'; import { inject as service } from '@ember/service'; -export default Component.extend({ - userSettings: service(), +export default class PageSizeSelect extends Component { + @service userSettings; - tagName: '', - pageSizeOptions: Object.freeze([10, 25, 50]), + tagName = ''; + pageSizeOptions = Object.freeze([10, 25, 50]); - onChange() {}, -}); + onChange() {} +} diff --git a/ui/app/controllers/allocations/allocation/index.js b/ui/app/controllers/allocations/allocation/index.js index ebccdfa75..e167cdbe6 100644 --- a/ui/app/controllers/allocations/allocation/index.js +++ b/ui/app/controllers/allocations/allocation/index.js @@ -1,58 +1,68 @@ /* eslint-disable ember/no-observers */ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -import { computed, observer } from '@ember/object'; +import { action, computed } from '@ember/object'; +import { observes } from '@ember-decorators/object'; import { computed as overridable } from 'ember-overridable-computed'; import { alias } from '@ember/object/computed'; import { task } from 'ember-concurrency'; import Sortable from 'nomad-ui/mixins/sortable'; import { lazyClick } from 'nomad-ui/helpers/lazy-click'; import { watchRecord } from 'nomad-ui/utils/properties/watch'; +import classic from 'ember-classic-decorator'; -export default Controller.extend(Sortable, { - token: service(), +@classic +export default class IndexController extends Controller.extend(Sortable) { + @service token; - queryParams: { - sortProperty: 'sort', - sortDescending: 'desc', - }, + queryParams = [ + { + sortProperty: 'sort', + }, + { + sortDescending: 'desc', + }, + ]; - sortProperty: 'name', - sortDescending: false, + sortProperty = 'name'; + sortDescending = false; - listToSort: alias('model.states'), - sortedStates: alias('listSorted'), + @alias('model.states') listToSort; + @alias('listSorted') sortedStates; // Set in the route - preempter: null, + preempter = null; - error: overridable(() => { + @overridable(function() { // { title, description } return null; - }), + }) + error; - network: alias('model.allocatedResources.networks.firstObject'), + @alias('model.allocatedResources.networks.firstObject') network; - services: computed('model.taskGroup.services.@each.name', function() { + @computed('model.taskGroup.services.@each.name') + get services() { return this.get('model.taskGroup.services').sortBy('name'); - }), + } onDismiss() { this.set('error', null); - }, + } - watchNext: watchRecord('allocation'), + @watchRecord('allocation') watchNext; - observeWatchNext: observer('model.nextAllocation.clientStatus', function() { + @observes('model.nextAllocation.clientStatus') + observeWatchNext() { const nextAllocation = this.model.nextAllocation; if (nextAllocation && nextAllocation.content) { this.watchNext.perform(nextAllocation); } else { this.watchNext.cancelAll(); } - }), + } - stopAllocation: task(function*() { + @task(function*() { try { yield this.model.stop(); // Eagerly update the allocation clientStatus to avoid flickering @@ -63,9 +73,10 @@ export default Controller.extend(Sortable, { description: 'Your ACL token does not grant allocation lifecycle permissions.', }); } - }), + }) + stopAllocation; - restartAllocation: task(function*() { + @task(function*() { try { yield this.model.restart(); } catch (err) { @@ -74,15 +85,16 @@ export default Controller.extend(Sortable, { description: 'Your ACL token does not grant allocation lifecycle permissions.', }); } - }), + }) + restartAllocation; - actions: { - gotoTask(allocation, task) { - this.transitionToRoute('allocations.allocation.task', task); - }, + @action + gotoTask(allocation, task) { + this.transitionToRoute('allocations.allocation.task', task); + } - taskClick(allocation, task, event) { - lazyClick([() => this.send('gotoTask', allocation, task), event]); - }, - }, -}); + @action + taskClick(allocation, task, event) { + lazyClick([() => this.send('gotoTask', allocation, task), event]); + } +} diff --git a/ui/app/controllers/clients/client.js b/ui/app/controllers/clients/client.js index 260fdc4fa..e1858f089 100644 --- a/ui/app/controllers/clients/client.js +++ b/ui/app/controllers/clients/client.js @@ -1,84 +1,99 @@ /* eslint-disable ember/no-observers */ import { alias } from '@ember/object/computed'; import Controller from '@ember/controller'; -import { computed, observer } from '@ember/object'; +import { action, computed } from '@ember/object'; +import { observes } from '@ember-decorators/object'; import { task } from 'ember-concurrency'; import Sortable from 'nomad-ui/mixins/sortable'; import Searchable from 'nomad-ui/mixins/searchable'; import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; +import classic from 'ember-classic-decorator'; -export default Controller.extend(Sortable, Searchable, { - queryParams: { - currentPage: 'page', - searchTerm: 'search', - sortProperty: 'sort', - sortDescending: 'desc', - onlyPreemptions: 'preemptions', - }, +@classic +export default class ClientController extends Controller.extend(Sortable, Searchable) { + queryParams = [ + { + currentPage: 'page', + }, + { + searchTerm: 'search', + }, + { + sortProperty: 'sort', + }, + { + sortDescending: 'desc', + }, + { + onlyPreemptions: 'preemptions', + }, + ]; // Set in the route - flagAsDraining: false, + flagAsDraining = false; - currentPage: 1, - pageSize: 8, + currentPage = 1; + pageSize = 8; - sortProperty: 'modifyIndex', - sortDescending: true, + sortProperty = 'modifyIndex'; + sortDescending = true; - searchProps: computed(function() { + @computed() + get searchProps() { return ['shortId', 'name']; - }), + } - onlyPreemptions: false, + onlyPreemptions = false; - visibleAllocations: computed( - 'model.allocations.[]', - 'preemptions.[]', - 'onlyPreemptions', - function() { - return this.onlyPreemptions ? this.preemptions : this.model.allocations; - } - ), + @computed('model.allocations.[]', 'preemptions.[]', 'onlyPreemptions') + get visibleAllocations() { + return this.onlyPreemptions ? this.preemptions : this.model.allocations; + } - listToSort: alias('visibleAllocations'), - listToSearch: alias('listSorted'), - sortedAllocations: alias('listSearched'), + @alias('visibleAllocations') listToSort; + @alias('listSorted') listToSearch; + @alias('listSearched') sortedAllocations; - eligibilityError: null, - stopDrainError: null, - drainError: null, - showDrainNotification: false, - showDrainUpdateNotification: false, - showDrainStoppedNotification: false, + eligibilityError = null; + stopDrainError = null; + drainError = null; + showDrainNotification = false; + showDrainUpdateNotification = false; + showDrainStoppedNotification = false; - preemptions: computed('model.allocations.@each.wasPreempted', function() { + @computed('model.allocations.@each.wasPreempted') + get preemptions() { return this.model.allocations.filterBy('wasPreempted'); - }), + } - sortedEvents: computed('model.events.@each.time', function() { + @computed('model.events.@each.time') + get sortedEvents() { return this.get('model.events') .sortBy('time') .reverse(); - }), + } - sortedDrivers: computed('model.drivers.@each.name', function() { + @computed('model.drivers.@each.name') + get sortedDrivers() { return this.get('model.drivers').sortBy('name'); - }), + } - sortedHostVolumes: computed('model.hostVolumes.@each.name', function() { + @computed('model.hostVolumes.@each.name') + get sortedHostVolumes() { return this.model.hostVolumes.sortBy('name'); - }), + } - setEligibility: task(function*(value) { + @(task(function*(value) { try { yield value ? this.model.setEligible() : this.model.setIneligible(); } catch (err) { const error = messageFromAdapterError(err) || 'Could not set eligibility'; this.set('eligibilityError', error); } - }).drop(), + }).drop()) + setEligibility; - stopDrain: task(function*() { + @(task(function*() { try { this.set('flagAsDraining', false); yield this.model.cancelDrain(); @@ -88,9 +103,10 @@ export default Controller.extend(Sortable, Searchable, { const error = messageFromAdapterError(err) || 'Could not stop drain'; this.set('stopDrainError', error); } - }).drop(), + }).drop()) + stopDrain; - forceDrain: task(function*() { + @(task(function*() { try { yield this.model.forceDrain({ IgnoreSystemJobs: this.model.drainStrategy.ignoreSystemJobs, @@ -99,32 +115,36 @@ export default Controller.extend(Sortable, Searchable, { const error = messageFromAdapterError(err) || 'Could not force drain'; this.set('drainError', error); } - }).drop(), + }).drop()) + forceDrain; - triggerDrainNotification: observer('model.isDraining', function() { + @observes('model.isDraining') + triggerDrainNotification() { if (!this.model.isDraining && this.flagAsDraining) { this.set('showDrainNotification', true); } this.set('flagAsDraining', this.model.isDraining); - }), + } - actions: { - gotoAllocation(allocation) { - this.transitionToRoute('allocations.allocation', allocation); - }, + @action + gotoAllocation(allocation) { + this.transitionToRoute('allocations.allocation', allocation); + } - setPreemptionFilter(value) { - this.set('onlyPreemptions', value); - }, + @action + setPreemptionFilter(value) { + this.set('onlyPreemptions', value); + } - drainNotify(isUpdating) { - this.set('showDrainUpdateNotification', isUpdating); - }, + @action + drainNotify(isUpdating) { + this.set('showDrainUpdateNotification', isUpdating); + } - drainError(err) { - const error = messageFromAdapterError(err) || 'Could not run drain'; - this.set('drainError', error); - }, - }, -}); + @action + drainError(err) { + const error = messageFromAdapterError(err) || 'Could not run drain'; + this.set('drainError', error); + } +} diff --git a/ui/app/controllers/exec.js b/ui/app/controllers/exec.js index d6ea565fe..d648753fc 100644 --- a/ui/app/controllers/exec.js +++ b/ui/app/controllers/exec.js @@ -1,36 +1,39 @@ import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; -import { computed } from '@ember/object'; +import { action, computed } from '@ember/object'; import { alias, mapBy, sort, uniq } from '@ember/object/computed'; import escapeTaskName from 'nomad-ui/utils/escape-task-name'; import ExecCommandEditorXtermAdapter from 'nomad-ui/utils/classes/exec-command-editor-xterm-adapter'; import ExecSocketXtermAdapter from 'nomad-ui/utils/classes/exec-socket-xterm-adapter'; import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; +import classic from 'ember-classic-decorator'; const ANSI_UI_GRAY_400 = '\x1b[38;2;142;150;163m'; const ANSI_WHITE = '\x1b[0m'; -export default Controller.extend({ - sockets: service(), - system: service(), - token: service(), +@classic +export default class ExecController extends Controller { + @service sockets; + @service system; + @service token; - queryParams: ['allocation'], + queryParams = ['allocation']; - command: localStorageProperty('nomadExecCommand', '/bin/bash'), - socketOpen: false, + @localStorageProperty('nomadExecCommand', '/bin/bash') command; + socketOpen = false; - pendingAndRunningAllocations: computed('model.allocations.@each.clientStatus', function() { + @computed('model.allocations.@each.clientStatus') + get pendingAndRunningAllocations() { return this.model.allocations.filter( allocation => allocation.clientStatus === 'pending' || allocation.clientStatus === 'running' ); - }), + } - pendingAndRunningTaskGroups: mapBy('pendingAndRunningAllocations', 'taskGroup'), - uniquePendingAndRunningTaskGroups: uniq('pendingAndRunningTaskGroups'), + @mapBy('pendingAndRunningAllocations', 'taskGroup') pendingAndRunningTaskGroups; + @uniq('pendingAndRunningTaskGroups') uniquePendingAndRunningTaskGroups; - taskGroupSorting: Object.freeze(['name']), - sortedTaskGroups: sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting'), + taskGroupSorting = Object.freeze(['name']); + @sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting') sortedTaskGroups; setUpTerminal(Terminal) { this.terminal = new Terminal({ fontFamily: 'monospace', fontWeight: '400' }); @@ -38,86 +41,85 @@ export default Controller.extend({ this.terminal.write(ANSI_UI_GRAY_400); this.terminal.writeln('Select a task to start your session.'); - }, + } - allocations: alias('model.allocations'), + @alias('model.allocations') allocations; - taskState: computed( + @computed( 'allocations.{[],@each.isActive}', 'allocationShortId', 'taskName', 'taskGroupName', 'allocation', - 'allocation.states.@each.{name,isRunning}', - function() { - if (!this.allocations) { - return false; - } - - let allocation; - - if (this.allocationShortId) { - allocation = this.allocations.findBy('shortId', this.allocationShortId); - } else { - allocation = this.allocations.find(allocation => - allocation.states - .filterBy('isActive') - .mapBy('name') - .includes(this.taskName) - ); - } - - if (allocation) { - return allocation.states.find(state => state.name === this.taskName); - } - - return; + 'allocation.states.@each.{name,isRunning}' + ) + get taskState() { + if (!this.allocations) { + return false; } - ), - actions: { - setTaskProperties({ allocationShortId, taskName, taskGroupName }) { - this.setProperties({ - allocationShortId, - taskName, - taskGroupName, - }); + let allocation; - if (this.taskState) { - this.terminal.write(ANSI_UI_GRAY_400); - this.terminal.writeln(''); + if (this.allocationShortId) { + allocation = this.allocations.findBy('shortId', this.allocationShortId); + } else { + allocation = this.allocations.find(allocation => + allocation.states + .filterBy('isActive') + .mapBy('name') + .includes(this.taskName) + ); + } - if (!allocationShortId) { - this.terminal.writeln( - 'Multiple instances of this task are running. The allocation below was selected by random draw.' - ); - this.terminal.writeln(''); - } + if (allocation) { + return allocation.states.find(state => state.name === this.taskName); + } - this.terminal.writeln('Customize your command, then hit ‘return’ to run.'); - this.terminal.writeln(''); - this.terminal.write( - `$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${ - this.taskState.allocation.shortId - } ` - ); - - this.terminal.write(ANSI_WHITE); - - this.terminal.write(this.command); - - if (this.commandEditorAdapter) { - this.commandEditorAdapter.destroy(); - } - - this.commandEditorAdapter = new ExecCommandEditorXtermAdapter( - this.terminal, - this.openAndConnectSocket.bind(this), - this.command + return undefined; + } + + @action + setTaskProperties({ allocationShortId, taskName, taskGroupName }) { + this.setProperties({ + allocationShortId, + taskName, + taskGroupName, + }); + + if (this.taskState) { + this.terminal.write(ANSI_UI_GRAY_400); + this.terminal.writeln(''); + + if (!allocationShortId) { + this.terminal.writeln( + 'Multiple instances of this task are running. The allocation below was selected by random draw.' ); + this.terminal.writeln(''); } - }, - }, + + this.terminal.writeln('Customize your command, then hit ‘return’ to run.'); + this.terminal.writeln(''); + this.terminal.write( + `$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${ + this.taskState.allocation.shortId + } ` + ); + + this.terminal.write(ANSI_WHITE); + + this.terminal.write(this.command); + + if (this.commandEditorAdapter) { + this.commandEditorAdapter.destroy(); + } + + this.commandEditorAdapter = new ExecCommandEditorXtermAdapter( + this.terminal, + this.openAndConnectSocket.bind(this), + this.command + ); + } + } openAndConnectSocket(command) { if (this.taskState) { @@ -129,5 +131,5 @@ export default Controller.extend({ } else { this.terminal.writeln(`Failed to open a socket because task ${this.taskName} is not active.`); } - }, -}); + } +} diff --git a/ui/app/controllers/settings/tokens.js b/ui/app/controllers/settings/tokens.js index 8caab695f..956268374 100644 --- a/ui/app/controllers/settings/tokens.js +++ b/ui/app/controllers/settings/tokens.js @@ -3,66 +3,69 @@ import { reads } from '@ember/object/computed'; import Controller from '@ember/controller'; import { getOwner } from '@ember/application'; import { alias } from '@ember/object/computed'; +import { action } from '@ember/object'; +import classic from 'ember-classic-decorator'; -export default Controller.extend({ - token: service(), - system: service(), - store: service(), +@classic +export default class Tokens extends Controller { + @service token; + @service system; + @service store; - secret: reads('token.secret'), + @reads('token.secret') secret; - tokenIsValid: false, - tokenIsInvalid: false, - tokenRecord: alias('token.selfToken'), + tokenIsValid = false; + tokenIsInvalid = false; + @alias('token.selfToken') tokenRecord; resetStore() { this.store.unloadAll(); - }, + } - actions: { - clearTokenProperties() { - this.token.setProperties({ - secret: undefined, - }); - this.setProperties({ - tokenIsValid: false, - tokenIsInvalid: false, - }); - this.resetStore(); - this.token.reset(); - }, + @action + clearTokenProperties() { + this.token.setProperties({ + secret: undefined, + }); + this.setProperties({ + tokenIsValid: false, + tokenIsInvalid: false, + }); + this.resetStore(); + this.token.reset(); + } - verifyToken() { - const { secret } = this; - const TokenAdapter = getOwner(this).lookup('adapter:token'); + @action + verifyToken() { + const { secret } = this; + const TokenAdapter = getOwner(this).lookup('adapter:token'); - this.set('token.secret', secret); + this.set('token.secret', secret); - TokenAdapter.findSelf().then( - () => { - // Clear out all data to ensure only data the new token is privileged to - // see is shown - this.system.reset(); - this.resetStore(); + TokenAdapter.findSelf().then( + () => { + // Clear out all data to ensure only data the new token is privileged to + // see is shown + this.system.reset(); + this.resetStore(); - // Refetch the token and associated policies - this.get('token.fetchSelfTokenAndPolicies') - .perform() - .catch(); + // Refetch the token and associated policies + this.get('token.fetchSelfTokenAndPolicies') + .perform() + .catch(); - this.setProperties({ - tokenIsValid: true, - tokenIsInvalid: false, - }); - }, - () => { - this.set('token.secret', undefined); - this.setProperties({ - tokenIsValid: false, - tokenIsInvalid: true, - }); - } - ); - }, - }, -}); + this.setProperties({ + tokenIsValid: true, + tokenIsInvalid: false, + }); + }, + () => { + this.set('token.secret', undefined); + this.setProperties({ + tokenIsValid: false, + tokenIsInvalid: true, + }); + } + ); + } +} diff --git a/ui/app/routes/application.js b/ui/app/routes/application.js index d97eb853c..1e7500924 100644 --- a/ui/app/routes/application.js +++ b/ui/app/routes/application.js @@ -3,24 +3,27 @@ import { next } from '@ember/runloop'; import Route from '@ember/routing/route'; import { AbortError } from '@ember-data/adapter/error'; import RSVP from 'rsvp'; +import { action } from '@ember/object'; +import classic from 'ember-classic-decorator'; -export default Route.extend({ - config: service(), - system: service(), - store: service(), - token: service(), +@classic +export default class ApplicationRoute extends Route { + @service config; + @service system; + @service store; + @service token; - queryParams: { + queryParams = { region: { refreshModel: true, }, - }, + }; resetController(controller, isExiting) { if (isExiting) { controller.set('error', null); } - }, + } beforeModel(transition) { const fetchSelfTokenAndPolicies = this.get('token.fetchSelfTokenAndPolicies') @@ -51,13 +54,13 @@ export default Route.extend({ return promises; }); - }, + } // Model is being used as a way to transfer the provided region // query param to update the controller state. model(params) { return params.region; - }, + } setupController(controller, model) { const queryParam = model; @@ -68,24 +71,25 @@ export default Route.extend({ }); } - return this._super(...arguments); - }, + return super.setupController(...arguments); + } - actions: { - didTransition() { - if (!this.get('config.isTest')) { - window.scrollTo(0, 0); - } - }, + @action + didTransition() { + if (!this.get('config.isTest')) { + window.scrollTo(0, 0); + } + } - willTransition() { - this.controllerFor('application').set('error', null); - }, + @action + willTransition() { + this.controllerFor('application').set('error', null); + } - error(error) { - if (!(error instanceof AbortError)) { - this.controllerFor('application').set('error', error); - } - }, - }, -}); + @action + error(error) { + if (!(error instanceof AbortError)) { + this.controllerFor('application').set('error', error); + } + } +} diff --git a/ui/app/routes/clients.js b/ui/app/routes/clients.js index 23c387284..404023e75 100644 --- a/ui/app/routes/clients.js +++ b/ui/app/routes/clients.js @@ -3,26 +3,28 @@ import Route from '@ember/routing/route'; import RSVP from 'rsvp'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; +import classic from 'ember-classic-decorator'; -export default Route.extend(WithForbiddenState, { - store: service(), - system: service(), +@classic +export default class ClientsRoute extends Route.extend(WithForbiddenState) { + @service store; + @service system; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Clients', args: ['clients.index'], }, - ]), + ]); beforeModel() { return this.get('system.leader'); - }, + } model() { return RSVP.hash({ nodes: this.store.findAll('node'), agents: this.store.findAll('agent'), }).catch(notifyForbidden(this)); - }, -}); + } +} diff --git a/ui/app/routes/csi/plugins.js b/ui/app/routes/csi/plugins.js index 30b181220..d6e6fefad 100644 --- a/ui/app/routes/csi/plugins.js +++ b/ui/app/routes/csi/plugins.js @@ -3,17 +3,17 @@ import Route from '@ember/routing/route'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; -export default Route.extend(WithForbiddenState, { - store: service(), +export default class PluginsRoute extends Route.extend(WithForbiddenState) { + @service store; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Storage', args: ['csi.index'], }, - ]), + ]); model() { return this.store.query('plugin', { type: 'csi' }).catch(notifyForbidden(this)); - }, -}); + } +} diff --git a/ui/app/routes/csi/volumes.js b/ui/app/routes/csi/volumes.js index fe49f7639..84548d8ca 100644 --- a/ui/app/routes/csi/volumes.js +++ b/ui/app/routes/csi/volumes.js @@ -2,23 +2,25 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; +import classic from 'ember-classic-decorator'; -export default Route.extend(WithForbiddenState, { - system: service(), - store: service(), +@classic +export default class VolumesRoute extends Route.extend(WithForbiddenState) { + @service system; + @service store; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Storage', args: ['csi.index'], }, - ]), + ]); - queryParams: { + queryParams = { volumeNamespace: { refreshModel: true, }, - }, + }; beforeModel(transition) { return this.get('system.namespaces').then(namespaces => { @@ -27,7 +29,7 @@ export default Route.extend(WithForbiddenState, { return namespaces; }); - }, + } model() { return this.store @@ -37,5 +39,5 @@ export default Route.extend(WithForbiddenState, { return volumes; }) .catch(notifyForbidden(this)); - }, -}); + } +} diff --git a/ui/app/routes/exec.js b/ui/app/routes/exec.js index 883c0f44d..4ad7fbea9 100644 --- a/ui/app/routes/exec.js +++ b/ui/app/routes/exec.js @@ -4,14 +4,16 @@ import notifyError from 'nomad-ui/utils/notify-error'; import { collect } from '@ember/object/computed'; import WithWatchers from 'nomad-ui/mixins/with-watchers'; import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch'; +import classic from 'ember-classic-decorator'; -export default Route.extend(WithWatchers, { - store: service(), - token: service(), +@classic +export default class ExecRoute extends Route.extend(WithWatchers) { + @service store; + @service token; serialize(model) { return { job_name: model.get('plainId') }; - }, + } model(params, transition) { const namespace = transition.to.queryParams.namespace || this.get('system.activeNamespace.id'); @@ -28,22 +30,22 @@ export default Route.extend(WithWatchers, { const xtermImport = import('xterm').then(module => module.Terminal); return Promise.all([jobPromise, xtermImport]); - }, + } setupController(controller, [job, Terminal]) { - this._super(controller, job); + super.setupController(controller, job); controller.setUpTerminal(Terminal); - }, + } startWatchers(controller, model) { if (model) { controller.set('watcher', this.watch.perform(model)); controller.set('watchAllocations', this.watchAllocations.perform(model)); } - }, + } - watch: watchRecord('job'), - watchAllocations: watchRelationship('allocations'), + @watchRecord('job') watch; + @watchRelationship('allocations') watchAllocations; - watchers: collect('watch', 'watchAllocations'), -}); + @collect('watch', 'watchAllocations') watchers; +} diff --git a/ui/app/routes/jobs.js b/ui/app/routes/jobs.js index 42b2e1cb5..e84f2efe7 100644 --- a/ui/app/routes/jobs.js +++ b/ui/app/routes/jobs.js @@ -2,23 +2,26 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; +import { action } from '@ember/object'; +import classic from 'ember-classic-decorator'; -export default Route.extend(WithForbiddenState, { - system: service(), - store: service(), +@classic +export default class JobsRoute extends Route.extend(WithForbiddenState) { + @service store; + @service system; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Jobs', args: ['jobs.index'], }, - ]), + ]); - queryParams: { + queryParams = { jobNamespace: { refreshModel: true, }, - }, + }; beforeModel(transition) { return this.get('system.namespaces').then(namespaces => { @@ -27,15 +30,14 @@ export default Route.extend(WithForbiddenState, { return namespaces; }); - }, + } model() { return this.store.findAll('job', { reload: true }).catch(notifyForbidden(this)); - }, + } - actions: { - refreshRoute() { - this.refresh(); - }, - }, -}); + @action + refreshRoute() { + this.refresh(); + } +} diff --git a/ui/app/routes/jobs/run.js b/ui/app/routes/jobs/run.js index 3f45bf006..417bbff97 100644 --- a/ui/app/routes/jobs/run.js +++ b/ui/app/routes/jobs/run.js @@ -1,33 +1,35 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; +import classic from 'ember-classic-decorator'; -export default Route.extend({ - can: service(), - store: service(), - system: service(), +@classic +export default class RunRoute extends Route { + @service can; + @service store; + @service system; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Run', args: ['jobs.run'], }, - ]), + ]); beforeModel() { if (this.can.cannot('run job')) { this.transitionTo('jobs'); } - }, + } model() { return this.store.createRecord('job', { namespace: this.get('system.activeNamespace'), }); - }, + } resetController(controller, isExiting) { if (isExiting) { controller.model.deleteRecord(); } - }, -}); + } +} diff --git a/ui/app/routes/servers.js b/ui/app/routes/servers.js index 62ee43b89..c48de5411 100644 --- a/ui/app/routes/servers.js +++ b/ui/app/routes/servers.js @@ -3,26 +3,28 @@ import Route from '@ember/routing/route'; import RSVP from 'rsvp'; import WithForbiddenState from 'nomad-ui/mixins/with-forbidden-state'; import notifyForbidden from 'nomad-ui/utils/notify-forbidden'; +import classic from 'ember-classic-decorator'; -export default Route.extend(WithForbiddenState, { - store: service(), - system: service(), +@classic +export default class ServersRoute extends Route.extend(WithForbiddenState) { + @service store; + @service system; - breadcrumbs: Object.freeze([ + breadcrumbs = Object.freeze([ { label: 'Servers', args: ['servers.index'], }, - ]), + ]); beforeModel() { return this.get('system.leader'); - }, + } model() { return RSVP.hash({ nodes: this.store.findAll('node'), agents: this.store.findAll('agent'), }).catch(notifyForbidden(this)); - }, -}); + } +} diff --git a/ui/app/serializers/agent.js b/ui/app/serializers/agent.js index 5cbe4c3ad..75b4ce111 100644 --- a/ui/app/serializers/agent.js +++ b/ui/app/serializers/agent.js @@ -1,12 +1,12 @@ import ApplicationSerializer from './application'; import AdapterError from '@ember-data/adapter/error'; -export default ApplicationSerializer.extend({ - attrs: { +export default class AgentSerializer extends ApplicationSerializer { + attrs = { datacenter: 'dc', address: 'Addr', serfPort: 'Port', - }, + }; normalize(typeHash, hash) { if (!hash) { @@ -24,14 +24,14 @@ export default ApplicationSerializer.extend({ hash.Region = hash.Tags && hash.Tags.region; hash.RpcPort = hash.Tags && hash.Tags.port; - return this._super(typeHash, hash); - }, + return super.normalize(typeHash, hash); + } normalizeResponse(store, typeClass, hash, ...args) { - return this._super(store, typeClass, hash.Members || [], ...args); - }, + return super.normalizeResponse(store, typeClass, hash.Members || [], ...args); + } normalizeSingleResponse(store, typeClass, hash, id, ...args) { - return this._super(store, typeClass, hash.findBy('Name', id), id, ...args); - }, -}); + return super.normalizeSingleResponse(store, typeClass, hash.findBy('Name', id), id, ...args); + } +} diff --git a/ui/app/serializers/allocation.js b/ui/app/serializers/allocation.js index 43ace830f..7095d786a 100644 --- a/ui/app/serializers/allocation.js +++ b/ui/app/serializers/allocation.js @@ -8,13 +8,13 @@ const taskGroupFromJob = (job, taskGroupName) => { return taskGroup ? taskGroup : null; }; -export default ApplicationSerializer.extend({ - system: service(), +export default class AllocationSerializer extends ApplicationSerializer { + @service system; - attrs: { + attrs = { taskGroupName: 'TaskGroup', states: 'TaskStates', - }, + }; normalize(typeHash, hash) { // Transform the map-based TaskStates object into an array-based @@ -63,6 +63,6 @@ export default ApplicationSerializer.extend({ // The Job definition for an allocation is only included in findRecord responses. hash.AllocationTaskGroup = !hash.Job ? null : taskGroupFromJob(hash.Job, hash.TaskGroup); - return this._super(typeHash, hash); - }, -}); + return super.normalize(typeHash, hash); + } +} diff --git a/ui/app/serializers/deployment.js b/ui/app/serializers/deployment.js index 456eb1b25..1c57286f3 100644 --- a/ui/app/serializers/deployment.js +++ b/ui/app/serializers/deployment.js @@ -1,11 +1,13 @@ import { get } from '@ember/object'; import { assign } from '@ember/polyfills'; import ApplicationSerializer from './application'; +import classic from 'ember-classic-decorator'; -export default ApplicationSerializer.extend({ - attrs: { +@classic +export default class DeploymentSerializer extends ApplicationSerializer { + attrs = { versionNumber: 'JobVersion', - }, + }; normalize(typeHash, hash) { if (hash) { @@ -29,8 +31,8 @@ export default ApplicationSerializer.extend({ hash.JobID = hash.JobForLatestID = JSON.stringify([hash.JobID, hash.Namespace]); } - return this._super(typeHash, hash); - }, + return super.normalize(typeHash, hash); + } extractRelationships(modelClass, hash) { const namespace = this.store.adapterFor(modelClass.modelName).get('namespace'); @@ -44,7 +46,7 @@ export default ApplicationSerializer.extend({ }, }, }, - this._super(modelClass, hash) + super.extractRelationships(modelClass, hash) ); - }, -}); + } +} diff --git a/ui/app/serializers/job-version.js b/ui/app/serializers/job-version.js index 4e250d5d8..bc2962a53 100644 --- a/ui/app/serializers/job-version.js +++ b/ui/app/serializers/job-version.js @@ -1,10 +1,10 @@ import { assign } from '@ember/polyfills'; import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class JobVersionSerializer extends ApplicationSerializer { + attrs = { number: 'Version', - }, + }; normalizeFindHasManyResponse(store, modelClass, hash, id, requestType) { const zippedVersions = hash.Versions.map((version, index) => @@ -16,6 +16,13 @@ export default ApplicationSerializer.extend({ SubmitTimeNanos: version.SubmitTime % 1000000, }) ); - return this._super(store, modelClass, zippedVersions, hash, id, requestType); - }, -}); + return super.normalizeFindHasManyResponse( + store, + modelClass, + zippedVersions, + hash, + id, + requestType + ); + } +} diff --git a/ui/app/serializers/job.js b/ui/app/serializers/job.js index a43badba1..ded935c00 100644 --- a/ui/app/serializers/job.js +++ b/ui/app/serializers/job.js @@ -2,10 +2,10 @@ import { assign } from '@ember/polyfills'; import ApplicationSerializer from './application'; import queryString from 'query-string'; -export default ApplicationSerializer.extend({ - attrs: { +export default class JobSerializer extends ApplicationSerializer { + attrs = { parameterized: 'ParameterizedJob', - }, + }; normalize(typeHash, hash) { hash.NamespaceID = hash.Namespace; @@ -45,8 +45,8 @@ export default ApplicationSerializer.extend({ }); } - return this._super(typeHash, hash); - }, + return super.normalize(typeHash, hash); + } extractRelationships(modelClass, hash) { const namespace = @@ -58,7 +58,7 @@ export default ApplicationSerializer.extend({ .buildURL(modelName, hash.ID, hash, 'findRecord') .split('?'); - return assign(this._super(...arguments), { + return assign(super.extractRelationships(...arguments), { allocations: { links: { related: buildURL(`${jobURL}/allocations`, { namespace }), @@ -85,8 +85,8 @@ export default ApplicationSerializer.extend({ }, }, }); - }, -}); + } +} function buildURL(path, queryParams) { const qpString = queryString.stringify(queryParams); diff --git a/ui/app/serializers/network.js b/ui/app/serializers/network.js index b5767df2e..3310db18c 100644 --- a/ui/app/serializers/network.js +++ b/ui/app/serializers/network.js @@ -1,12 +1,12 @@ import ApplicationSerializer from './application'; import isIp from 'is-ip'; -export default ApplicationSerializer.extend({ - attrs: { +export default class NetworkSerializer extends ApplicationSerializer { + attrs = { cidr: 'CIDR', ip: 'IP', mbits: 'MBits', - }, + }; normalize(typeHash, hash) { const ip = hash.IP; @@ -31,6 +31,6 @@ export default ApplicationSerializer.extend({ hash.Ports = reservedPorts.concat(dynamicPorts).sortBy('name'); - return this._super(...arguments); - }, -}); + return super.normalize(...arguments); + } +} diff --git a/ui/app/serializers/node-event.js b/ui/app/serializers/node-event.js index 606c6dadd..169b65faf 100644 --- a/ui/app/serializers/node-event.js +++ b/ui/app/serializers/node-event.js @@ -1,7 +1,7 @@ import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class NodeEventSerializer extends ApplicationSerializer { + attrs = { time: 'Timestamp', - }, -}); + }; +} diff --git a/ui/app/serializers/node.js b/ui/app/serializers/node.js index 1084f442c..4eb05b976 100644 --- a/ui/app/serializers/node.js +++ b/ui/app/serializers/node.js @@ -2,13 +2,13 @@ import { assign } from '@ember/polyfills'; import { inject as service } from '@ember/service'; import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - config: service(), +export default class NodeSerializer extends ApplicationSerializer { + @service config; - attrs: { + attrs = { isDraining: 'Drain', httpAddr: 'HTTPAddr', - }, + }; normalize(modelClass, hash) { // Transform map-based objects into array-based fragment lists @@ -20,8 +20,8 @@ export default ApplicationSerializer.extend({ const hostVolumes = hash.HostVolumes || {}; hash.HostVolumes = Object.keys(hostVolumes).map(key => hostVolumes[key]); - return this._super(modelClass, hash); - }, + return super.normalize(modelClass, hash); + } extractRelationships(modelClass, hash) { const { modelName } = modelClass; @@ -36,5 +36,5 @@ export default ApplicationSerializer.extend({ }, }, }; - }, -}); + } +} diff --git a/ui/app/serializers/resources.js b/ui/app/serializers/resources.js index a657ed43b..43c9e23b7 100644 --- a/ui/app/serializers/resources.js +++ b/ui/app/serializers/resources.js @@ -1,10 +1,10 @@ import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class ResourcesSerializer extends ApplicationSerializer { + attrs = { cpu: 'CPU', memory: 'MemoryMB', disk: 'DiskMB', iops: 'IOPS', - }, -}); + }; +} diff --git a/ui/app/serializers/service.js b/ui/app/serializers/service.js index da532204c..107c79f28 100644 --- a/ui/app/serializers/service.js +++ b/ui/app/serializers/service.js @@ -1,15 +1,15 @@ import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class ServiceSerializer extends ApplicationSerializer { + attrs = { connect: 'Connect', - }, + }; normalize(typeHash, hash) { if (!hash.Tags) { hash.Tags = []; } - return this._super(typeHash, hash); - }, -}); + return super.normalize(typeHash, hash); + } +} diff --git a/ui/app/serializers/task-event.js b/ui/app/serializers/task-event.js index f9955ed45..dfa43f4b6 100644 --- a/ui/app/serializers/task-event.js +++ b/ui/app/serializers/task-event.js @@ -1,9 +1,9 @@ import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class TaskEventSerializer extends ApplicationSerializer { + attrs = { message: 'DisplayMessage', - }, + }; normalize(typeHash, hash) { // Time is in the form of nanoseconds since epoch, but JS dates @@ -13,6 +13,6 @@ export default ApplicationSerializer.extend({ hash.TimeNanos = hash.Time % 1000000; hash.Time = Math.floor(hash.Time / 1000000); - return this._super(typeHash, hash); - }, -}); + return super.normalize(typeHash, hash); + } +} diff --git a/ui/app/serializers/token.js b/ui/app/serializers/token.js index 06a73676c..0e3beb302 100644 --- a/ui/app/serializers/token.js +++ b/ui/app/serializers/token.js @@ -1,16 +1,16 @@ import { copy } from 'ember-copy'; import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - primaryKey: 'AccessorID', +export default class TokenSerializer extends ApplicationSerializer { + primaryKey = 'AccessorID'; - attrs: { + attrs = { secret: 'SecretID', - }, + }; normalize(typeHash, hash) { hash.PolicyIDs = hash.Policies; hash.PolicyNames = copy(hash.Policies); - return this._super(typeHash, hash); - }, -}); + return super.normalize(typeHash, hash); + } +} diff --git a/ui/app/serializers/volume.js b/ui/app/serializers/volume.js index 78e5a49b7..f570eb19d 100644 --- a/ui/app/serializers/volume.js +++ b/ui/app/serializers/volume.js @@ -1,12 +1,12 @@ import { set, get } from '@ember/object'; import ApplicationSerializer from './application'; -export default ApplicationSerializer.extend({ - attrs: { +export default class VolumeSerializer extends ApplicationSerializer { + attrs = { externalId: 'ExternalID', - }, + }; - embeddedRelationships: Object.freeze(['writeAllocations', 'readAllocations']), + embeddedRelationships = Object.freeze(['writeAllocations', 'readAllocations']); // Volumes treat Allocations as embedded records. Ember has an // EmbeddedRecords mixin, but it assumes an application is using @@ -35,15 +35,15 @@ export default ApplicationSerializer.extend({ hash.ReadAllocations = Object.keys(readAllocs).map(bindIDToAlloc(readAllocs)); hash.WriteAllocations = Object.keys(writeAllocs).map(bindIDToAlloc(writeAllocs)); - const normalizedHash = this._super(typeHash, hash); + const normalizedHash = super.normalize(typeHash, hash); return this.extractEmbeddedRecords(this, this.store, typeHash, normalizedHash); - }, + } keyForRelationship(attr, relationshipType) { //Embedded relationship attributes don't end in IDs if (this.embeddedRelationships.includes(attr)) return attr.capitalize(); - return this._super(attr, relationshipType); - }, + return super.keyForRelationship(attr, relationshipType); + } // Convert the embedded relationship arrays into JSONAPI included records extractEmbeddedRecords(serializer, store, typeHash, partial) { @@ -84,7 +84,7 @@ export default ApplicationSerializer.extend({ }); return partial; - }, + } normalizeEmbeddedRelationship(store, relationshipMeta, relationshipHash) { const modelName = relationshipMeta.type; @@ -92,5 +92,5 @@ export default ApplicationSerializer.extend({ const serializer = store.serializerFor(modelName); return serializer.normalize(modelClass, relationshipHash, null); - }, -}); + } +} diff --git a/ui/app/utils/classes/stream-logger.js b/ui/app/utils/classes/stream-logger.js index 98e4fb4f3..7105c09f3 100644 --- a/ui/app/utils/classes/stream-logger.js +++ b/ui/app/utils/classes/stream-logger.js @@ -4,19 +4,22 @@ import TextDecoder from 'nomad-ui/utils/classes/text-decoder'; import { decode } from 'nomad-ui/utils/stream-frames'; import AbstractLogger from './abstract-logger'; import { fetchFailure } from './log'; +import classic from 'ember-classic-decorator'; -export default EmberObject.extend(AbstractLogger, { - reader: null, +@classic +export default class StreamLogger extends EmberObject.extend(AbstractLogger) { + reader = null; - additionalParams: computed(function() { + @computed() + get additionalParams() { return { follow: true, }; - }), + } start() { return this.poll.perform(); - }, + } stop() { const reader = this.reader; @@ -24,9 +27,9 @@ export default EmberObject.extend(AbstractLogger, { reader.cancel(); } return this.poll.cancelAll(); - }, + } - poll: task(function*() { + @task(function*() { const url = this.fullUrl; const logFetch = this.logFetch; @@ -81,8 +84,11 @@ export default EmberObject.extend(AbstractLogger, { } }); } - }), -}).reopenClass({ + }) + poll; +} + +StreamLogger.reopenClass({ isSupported: !!window.ReadableStream && !isSafari(), }); diff --git a/ui/app/utils/no-leader-error.js b/ui/app/utils/no-leader-error.js index 7b70918db..c531479e5 100644 --- a/ui/app/utils/no-leader-error.js +++ b/ui/app/utils/no-leader-error.js @@ -2,6 +2,6 @@ import AdapterError from '@ember-data/adapter/error'; export const NO_LEADER = 'No cluster leader'; -export default AdapterError.extend({ - message: NO_LEADER, -}); +export default class NoLeaderError extends AdapterError { + message = NO_LEADER; +}