github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/clients/client/index.js (about) 1 /* eslint-disable ember/no-observers */ 2 /* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */ 3 import { alias } from '@ember/object/computed'; 4 import Controller from '@ember/controller'; 5 import { action, computed } from '@ember/object'; 6 import { observes } from '@ember-decorators/object'; 7 import { scheduleOnce } from '@ember/runloop'; 8 import { task } from 'ember-concurrency'; 9 import intersection from 'lodash.intersection'; 10 import Sortable from 'nomad-ui/mixins/sortable'; 11 import Searchable from 'nomad-ui/mixins/searchable'; 12 import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; 13 import { 14 serialize, 15 deserializedQueryParam as selection, 16 } from 'nomad-ui/utils/qp-serialize'; 17 import classic from 'ember-classic-decorator'; 18 import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; 19 20 @classic 21 export default class ClientController extends Controller.extend( 22 Sortable, 23 Searchable 24 ) { 25 queryParams = [ 26 { 27 currentPage: 'page', 28 }, 29 { 30 searchTerm: 'search', 31 }, 32 { 33 sortProperty: 'sort', 34 }, 35 { 36 sortDescending: 'desc', 37 }, 38 { 39 onlyPreemptions: 'preemptions', 40 }, 41 { 42 qpNamespace: 'namespace', 43 }, 44 { 45 qpJob: 'job', 46 }, 47 { 48 qpStatus: 'status', 49 }, 50 'activeTask', 51 ]; 52 53 // Set in the route 54 flagAsDraining = false; 55 56 qpNamespace = ''; 57 qpJob = ''; 58 qpStatus = ''; 59 currentPage = 1; 60 pageSize = 8; 61 activeTask = null; 62 63 sortProperty = 'modifyIndex'; 64 sortDescending = true; 65 66 @localStorageProperty('nomadShowSubTasks', false) showSubTasks; 67 68 @action 69 toggleShowSubTasks(e) { 70 e.preventDefault(); 71 this.set('showSubTasks', !this.get('showSubTasks')); 72 } 73 74 @computed() 75 get searchProps() { 76 return ['shortId', 'name']; 77 } 78 79 onlyPreemptions = false; 80 81 @computed('model.allocations.[]', 'preemptions.[]', 'onlyPreemptions') 82 get visibleAllocations() { 83 return this.onlyPreemptions ? this.preemptions : this.model.allocations; 84 } 85 86 @computed( 87 'visibleAllocations.[]', 88 'selectionNamespace', 89 'selectionJob', 90 'selectionStatus' 91 ) 92 get filteredAllocations() { 93 const { selectionNamespace, selectionJob, selectionStatus } = this; 94 95 return this.visibleAllocations.filter((alloc) => { 96 if ( 97 selectionNamespace.length && 98 !selectionNamespace.includes(alloc.get('namespace')) 99 ) { 100 return false; 101 } 102 if ( 103 selectionJob.length && 104 !selectionJob.includes(alloc.get('plainJobId')) 105 ) { 106 return false; 107 } 108 if ( 109 selectionStatus.length && 110 !selectionStatus.includes(alloc.clientStatus) 111 ) { 112 return false; 113 } 114 return true; 115 }); 116 } 117 118 @alias('filteredAllocations') listToSort; 119 @alias('listSorted') listToSearch; 120 @alias('listSearched') sortedAllocations; 121 122 @selection('qpNamespace') selectionNamespace; 123 @selection('qpJob') selectionJob; 124 @selection('qpStatus') selectionStatus; 125 126 eligibilityError = null; 127 stopDrainError = null; 128 drainError = null; 129 showDrainNotification = false; 130 showDrainUpdateNotification = false; 131 showDrainStoppedNotification = false; 132 133 @computed('model.allocations.@each.wasPreempted') 134 get preemptions() { 135 return this.model.allocations.filterBy('wasPreempted'); 136 } 137 138 @computed('model.events.@each.time') 139 get sortedEvents() { 140 return this.get('model.events').sortBy('time').reverse(); 141 } 142 143 @computed('model.drivers.@each.name') 144 get sortedDrivers() { 145 return this.get('model.drivers').sortBy('name'); 146 } 147 148 @computed('model.hostVolumes.@each.name') 149 get sortedHostVolumes() { 150 return this.model.hostVolumes.sortBy('name'); 151 } 152 153 @(task(function* (value) { 154 try { 155 yield value ? this.model.setEligible() : this.model.setIneligible(); 156 } catch (err) { 157 const error = messageFromAdapterError(err) || 'Could not set eligibility'; 158 this.set('eligibilityError', error); 159 } 160 }).drop()) 161 setEligibility; 162 163 @(task(function* () { 164 try { 165 this.set('flagAsDraining', false); 166 yield this.model.cancelDrain(); 167 this.set('showDrainStoppedNotification', true); 168 } catch (err) { 169 this.set('flagAsDraining', true); 170 const error = messageFromAdapterError(err) || 'Could not stop drain'; 171 this.set('stopDrainError', error); 172 } 173 }).drop()) 174 stopDrain; 175 176 @(task(function* () { 177 try { 178 yield this.model.forceDrain({ 179 IgnoreSystemJobs: this.model.drainStrategy.ignoreSystemJobs, 180 }); 181 } catch (err) { 182 const error = messageFromAdapterError(err) || 'Could not force drain'; 183 this.set('drainError', error); 184 } 185 }).drop()) 186 forceDrain; 187 188 @observes('model.isDraining') 189 triggerDrainNotification() { 190 if (!this.model.isDraining && this.flagAsDraining) { 191 this.set('showDrainNotification', true); 192 } 193 194 this.set('flagAsDraining', this.model.isDraining); 195 } 196 197 @action 198 gotoAllocation(allocation) { 199 this.transitionToRoute('allocations.allocation', allocation); 200 } 201 202 @action 203 setPreemptionFilter(value) { 204 this.set('onlyPreemptions', value); 205 } 206 207 @action 208 drainNotify(isUpdating) { 209 this.set('showDrainUpdateNotification', isUpdating); 210 } 211 212 @action 213 setDrainError(err) { 214 const error = messageFromAdapterError(err) || 'Could not run drain'; 215 this.set('drainError', error); 216 } 217 218 get optionsAllocationStatus() { 219 return [ 220 { key: 'pending', label: 'Pending' }, 221 { key: 'running', label: 'Running' }, 222 { key: 'complete', label: 'Complete' }, 223 { key: 'failed', label: 'Failed' }, 224 { key: 'lost', label: 'Lost' }, 225 { key: 'unknown', label: 'Unknown' }, 226 ]; 227 } 228 229 @computed('model.allocations.[]', 'selectionJob', 'selectionNamespace') 230 get optionsJob() { 231 // Only show options for jobs in the selected namespaces, if any. 232 const ns = this.selectionNamespace; 233 const jobs = Array.from( 234 new Set( 235 this.model.allocations 236 .filter((a) => ns.length === 0 || ns.includes(a.namespace)) 237 .mapBy('plainJobId') 238 ) 239 ).compact(); 240 241 // Update query param when the list of jobs changes. 242 scheduleOnce('actions', () => { 243 // eslint-disable-next-line ember/no-side-effects 244 this.set('qpJob', serialize(intersection(jobs, this.selectionJob))); 245 }); 246 247 return jobs.sort().map((job) => ({ key: job, label: job })); 248 } 249 250 @computed('model.allocations.[]', 'selectionNamespace') 251 get optionsNamespace() { 252 const ns = Array.from( 253 new Set(this.model.allocations.mapBy('namespace')) 254 ).compact(); 255 256 // Update query param when the list of namespaces changes. 257 scheduleOnce('actions', () => { 258 // eslint-disable-next-line ember/no-side-effects 259 this.set( 260 'qpNamespace', 261 serialize(intersection(ns, this.selectionNamespace)) 262 ); 263 }); 264 265 return ns.sort().map((n) => ({ key: n, label: n })); 266 } 267 268 setFacetQueryParam(queryParam, selection) { 269 this.set(queryParam, serialize(selection)); 270 } 271 272 @action 273 setActiveTaskQueryParam(task) { 274 if (task) { 275 this.set('activeTask', `${task.allocation.id}-${task.name}`); 276 } else { 277 this.set('activeTask', null); 278 } 279 } 280 }