Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: CSI Plugins Pages #7872

Merged
merged 27 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83baebc
Add constraints table to the volume detail page
DingoEatingFuzz May 2, 2020
572cf0d
Update plugin model and serializer to match final API
DingoEatingFuzz May 2, 2020
70fe2b3
Add a subnav to the volumes page
DingoEatingFuzz May 2, 2020
f07a44d
Set up routes, controllers, and template basics for the plugins page
DingoEatingFuzz May 2, 2020
eaa107e
Clean up the csi volume page
DingoEatingFuzz May 3, 2020
6c262dd
Plugins table on the plugins list page
DingoEatingFuzz May 3, 2020
5d34381
Model out the rest of the CSI Plugin properties
DingoEatingFuzz May 3, 2020
96f86d9
Setup the plugin detail page
DingoEatingFuzz May 3, 2020
64fa26b
Separate AllocationStat component for containing the multiple states …
DingoEatingFuzz May 4, 2020
6ad5243
Refactor AllocationRow to use AllocationStat
DingoEatingFuzz May 4, 2020
0dd5882
Update storage controller mirage code to accommodate EDMF's lack of r…
DingoEatingFuzz May 4, 2020
c04b5d2
Add short option to date formatters
DingoEatingFuzz May 4, 2020
3999d45
Emulate belongsTo relationship in storage fragments
DingoEatingFuzz May 4, 2020
16296f1
New PluginAllocationRow derivative of AllocationRow
DingoEatingFuzz May 4, 2020
865e285
Use the correct plugin property for the breadcrumb
DingoEatingFuzz May 4, 2020
e6e5755
Add icons to the plugin alloc row component
DingoEatingFuzz May 4, 2020
d0d1c1f
Set color in addition to fill for the icon class
DingoEatingFuzz May 4, 2020
bcc6562
Add a nodes table as well
DingoEatingFuzz May 4, 2020
4f4bc6a
Correct the table headers for dates on the volume page
DingoEatingFuzz May 4, 2020
092b05d
Page object for Plugins List
DingoEatingFuzz May 5, 2020
e984bbc
Test coverage for the plugins list page
DingoEatingFuzz May 5, 2020
9632aac
Sort allocations on the plugin detail page
DingoEatingFuzz May 5, 2020
75a61e6
Plugin detail page object
DingoEatingFuzz May 5, 2020
7c373a2
Refactor AllocationRow qualifyAllocation
DingoEatingFuzz May 5, 2020
75a150f
Use lazyClick to avoid multiple transitionToRoutes being in flight as…
DingoEatingFuzz May 5, 2020
6b46b28
Don't wrap between icons and health text
DingoEatingFuzz May 5, 2020
877cadf
Acceptance test for PluginDetail
DingoEatingFuzz May 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions ui/app/components/allocation-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,24 +71,22 @@ export default Component.extend({
}).drop(),
});

function qualifyAllocation() {
async function qualifyAllocation() {
const allocation = this.allocation;

// Make sure the allocation is a complete record and not a partial so we
// can show information such as preemptions and rescheduled allocation.
return allocation.reload().then(() => {
this.fetchStats.perform();
await allocation.reload();

if (allocation.get('job.isPending')) {
// Make sure the job is loaded before starting the stats tracker
await allocation.get('job');
} else if (!allocation.get('taskGroup')) {
// Make sure that the job record in the store for this allocation
// is complete and not a partial from the list endpoint
if (
allocation &&
allocation.get('job') &&
!allocation.get('job.isPending') &&
!allocation.get('taskGroup')
) {
const job = allocation.get('job.content');
job && job.reload();
}
});
const job = allocation.get('job.content');
if (job) await job.reload();
}

this.fetchStats.perform();
}
44 changes: 44 additions & 0 deletions ui/app/components/allocation-stat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import { formatBytes } from 'nomad-ui/helpers/format-bytes';

export default Component.extend({
tagName: '',

allocation: null,
statsTracker: null,
isLoading: false,
error: null,
metric: 'memory', // Either memory or cpu

statClass: computed('metric', function() {
return this.metric === 'cpu' ? 'is-info' : 'is-danger';
}),

cpu: alias('statsTracker.cpu.lastObject'),
memory: alias('statsTracker.memory.lastObject'),

stat: computed('metric', 'cpu', 'memory', function() {
const { metric } = this;
if (metric === 'cpu' || metric === 'memory') {
return this[this.metric];
}
}),

formattedStat: computed('metric', 'stat.used', function() {
if (!this.stat) return;
if (this.metric === 'memory') return formatBytes([this.stat.used]);
return this.stat.used;
}),

formattedReserved: computed(
'metric',
'statsTracker.reservedMemory',
'statsTracker.reservedCPU',
function() {
if (this.metric === 'memory') return `${this.statsTracker.reservedMemory} MiB`;
if (this.metric === 'cpu') return `${this.statsTracker.reservedCPU} MHz`;
}
),
});
7 changes: 7 additions & 0 deletions ui/app/components/plugin-allocation-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { alias } from '@ember/object/computed';
import AllocationRow from 'nomad-ui/components/allocation-row';

export default AllocationRow.extend({
pluginAllocation: null,
allocation: alias('pluginAllocation.allocation'),
});
5 changes: 5 additions & 0 deletions ui/app/controllers/csi/plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Controller from '@ember/controller';

export default Controller.extend({
isForbidden: false,
});
40 changes: 40 additions & 0 deletions ui/app/controllers/csi/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { inject as service } from '@ember/service';
import { alias, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
import { lazyClick } from 'nomad-ui/helpers/lazy-click';

export default Controller.extend(SortableFactory([]), {
userSettings: service(),
pluginsController: controller('csi/plugins'),

isForbidden: alias('pluginsController.isForbidden'),

queryParams: {
currentPage: 'page',
sortProperty: 'sort',
sortDescending: 'desc',
},

currentPage: 1,
pageSize: readOnly('userSettings.pageSize'),

sortProperty: 'id',
sortDescending: false,

listToSort: alias('model'),
sortedPlugins: alias('listSorted'),

// TODO: Remove once this page gets search capability
resetPagination() {
if (this.currentPage != null) {
this.set('currentPage', 1);
}
},

actions: {
gotoPlugin(plugin, event) {
lazyClick([() => this.transitionToRoute('csi.plugins.plugin', plugin.plainId), event]);
},
},
});
18 changes: 18 additions & 0 deletions ui/app/controllers/csi/plugins/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';

export default Controller.extend({
sortedControllers: computed('model.controllers.@each.updateTime', function() {
return this.model.controllers.sortBy('updateTime').reverse();
}),

sortedNodes: computed('model.nodes.@each.updateTime', function() {
return this.model.nodes.sortBy('updateTime').reverse();
}),

actions: {
gotoAllocation(allocation) {
this.transitionToRoute('allocations.allocation', allocation);
},
},
});
8 changes: 6 additions & 2 deletions ui/app/controllers/csi/volumes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { computed } from '@ember/object';
import { alias, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import SortableFactory from 'nomad-ui/mixins/sortable-factory';
import { lazyClick } from 'nomad-ui/helpers/lazy-click';

export default Controller.extend(
SortableFactory([
Expand Down Expand Up @@ -58,8 +59,11 @@ export default Controller.extend(
},

actions: {
gotoVolume(volume) {
this.transitionToRoute('csi.volumes.volume', volume.get('plainId'));
gotoVolume(volume, event) {
lazyClick([
() => this.transitionToRoute('csi.volumes.volume', volume.get('plainId')),
event,
]);
},
},
}
Expand Down
1 change: 1 addition & 0 deletions ui/app/controllers/csi/volumes/volume.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inject as service } from '@ember/service';
import { computed } from '@ember/object';

export default Controller.extend({
// Used in the template
system: service(),

sortedReadAllocations: computed('model.readAllocations.@each.modifyIndex', function() {
Expand Down
4 changes: 2 additions & 2 deletions ui/app/helpers/format-month-ts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import moment from 'moment';
import Helper from '@ember/component/helper';

export function formatMonthTs([date]) {
const format = 'MMM DD HH:mm:ss ZZ';
export function formatMonthTs([date], options = {}) {
const format = options.short ? 'MMM D' : 'MMM DD HH:mm:ss ZZ';
return moment(date).format(format);
}

Expand Down
4 changes: 2 additions & 2 deletions ui/app/helpers/format-ts.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import moment from 'moment';
import Helper from '@ember/component/helper';

export function formatTs([date]) {
const format = "MMM DD, 'YY HH:mm:ss ZZ";
export function formatTs([date], options = {}) {
const format = options.short ? 'MMM D' : "MMM DD, 'YY HH:mm:ss ZZ";
return moment(date).format(format);
}

Expand Down
23 changes: 20 additions & 3 deletions ui/app/models/plugin.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import { computed } from '@ember/object';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
// import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { fragmentArray } from 'ember-data-model-fragments/attributes';

export default Model.extend({
plainId: attr('string'),

topologies: attr(),
provider: attr('string'),
version: attr('string'),

controllers: fragmentArray('storage-controller', { defaultValue: () => [] }),
nodes: fragmentArray('storage-node', { defaultValue: () => [] }),

controllerRequired: attr('boolean'),
controllersHealthy: attr('number'),
controllersExpected: attr('number'),

controllersHealthyProportion: computed('controllersHealthy', 'controllersExpected', function() {
return this.controllersHealthy / this.controllersExpected;
}),

nodesHealthy: attr('number'),
nodesExpected: attr('number'),

// controllers: fragmentArray('storage-controller', { defaultValue: () => [] }),
// nodes: fragmentArray('storage-node', { defaultValue: () => [] }),
nodesHealthyProportion: computed('nodesHealthy', 'nodesExpected', function() {
return this.nodesHealthy / this.nodesExpected;
}),
});
14 changes: 13 additions & 1 deletion ui/app/models/storage-controller.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { computed } from '@ember/object';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import Fragment from 'ember-data-model-fragments/fragment';
import { fragmentOwner } from 'ember-data-model-fragments/attributes';
import PromiseObject from 'nomad-ui/utils/classes/promise-object';

export default Fragment.extend({
plugin: fragmentOwner(),

node: belongsTo('node'),
allocation: belongsTo('allocation'),
allocID: attr('string'),

// Model fragments don't support relationships, but with an allocation ID
// a "belongsTo" can be sufficiently mocked.
allocation: computed('allocID', function() {
if (!this.allocID) return null;
return PromiseObject.create({
promise: this.store.findRecord('allocation', this.allocID),
reload: () => this.store.findRecord('allocation', this.allocID),
});
}),

provider: attr('string'),
version: attr('string'),
Expand Down
14 changes: 13 additions & 1 deletion ui/app/models/storage-node.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { computed } from '@ember/object';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import Fragment from 'ember-data-model-fragments/fragment';
import { fragmentOwner } from 'ember-data-model-fragments/attributes';
import PromiseObject from 'nomad-ui/utils/classes/promise-object';

export default Fragment.extend({
plugin: fragmentOwner(),

node: belongsTo('node'),
allocation: belongsTo('allocation'),
allocID: attr('string'),

// Model fragments don't support relationships, but with an allocation ID
// a "belongsTo" can be sufficiently mocked.
allocation: computed('allocID', function() {
if (!this.allocID) return null;
return PromiseObject.create({
promise: this.store.findRecord('allocation', this.allocID),
reload: () => this.store.findRecord('allocation', this.allocID),
});
}),

provider: attr('string'),
version: attr('string'),
Expand Down
4 changes: 4 additions & 0 deletions ui/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Router.map(function() {
this.route('volumes', function() {
this.route('volume', { path: '/:volume_name' });
});

this.route('plugins', function() {
this.route('plugin', { path: '/:plugin_name' });
});
});

this.route('allocations', function() {
Expand Down
19 changes: 19 additions & 0 deletions ui/app/routes/csi/plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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';

export default Route.extend(WithForbiddenState, {
store: service(),

breadcrumbs: [
{
label: 'Storage',
args: ['csi.index'],
},
],

model() {
return this.store.query('plugin', { type: 'csi' }).catch(notifyForbidden(this));
},
});
13 changes: 13 additions & 0 deletions ui/app/routes/csi/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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';

export default Route.extend(WithWatchers, {
startWatchers(controller) {
controller.set('modelWatch', this.watch.perform({ type: 'csi' }));
},

watch: watchQuery('plugin'),
watchers: collect('watch'),
});
41 changes: 41 additions & 0 deletions ui/app/routes/csi/plugins/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';

export default Route.extend(WithWatchers, {
store: service(),
system: service(),

breadcrumbs: plugin => [
{
label: 'Plugins',
args: ['csi.plugins'],
},
{
label: plugin.plainId,
args: ['csi.plugins.plugin', plugin.plainId],
},
],

startWatchers(controller, model) {
if (!model) return;

controller.set('watchers', {
model: this.watch.perform(model),
});
},

serialize(model) {
return { plugin_name: model.get('plainId') };
},

model(params) {
return this.store.findRecord('plugin', `csi/${params.plugin_name}`).catch(notifyError(this));
},

watch: watchRecord('plugin'),
watchers: collect('watch'),
});
Loading