github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/evaluations/index.js (about) 1 import { getOwner } from '@ember/application'; 2 import Controller from '@ember/controller'; 3 import { action } from '@ember/object'; 4 import { tracked } from '@glimmer/tracking'; 5 import { schedule } from '@ember/runloop'; 6 import { inject as service } from '@ember/service'; 7 import { useMachine } from 'ember-statecharts'; 8 import { use } from 'ember-usable'; 9 import evaluationsMachine from '../../machines/evaluations'; 10 11 const ALL_NAMESPACE_WILDCARD = '*'; 12 13 export default class EvaluationsController extends Controller { 14 @service store; 15 @service userSettings; 16 17 // We use statecharts here to manage complex user flows for the sidebar logic 18 @use 19 statechart = useMachine(evaluationsMachine).withConfig({ 20 services: { 21 loadEvaluation: this.loadEvaluation, 22 }, 23 actions: { 24 updateEvaluationQueryParameter: this.updateEvaluationQueryParameter, 25 removeCurrentEvaluationQueryParameter: 26 this.removeCurrentEvaluationQueryParameter, 27 }, 28 guards: { 29 sidebarIsOpen: this._sidebarIsOpen, 30 }, 31 }); 32 33 queryParams = [ 34 'nextToken', 35 'currentEval', 36 'pageSize', 37 'status', 38 { qpNamespace: 'namespace' }, 39 'type', 40 'searchTerm', 41 ]; 42 @tracked currentEval = null; 43 44 @action 45 _sidebarIsOpen() { 46 return !!this.currentEval; 47 } 48 49 @action 50 async loadEvaluation(context, { evaluation }) { 51 let evaluationId; 52 if (evaluation?.id) { 53 evaluationId = evaluation.id; 54 } else { 55 evaluationId = this.currentEval; 56 } 57 58 return this.store.findRecord('evaluation', evaluationId, { 59 reload: true, 60 adapterOptions: { related: true }, 61 }); 62 } 63 64 @action 65 async handleEvaluationClick(evaluation, e) { 66 if ( 67 e instanceof MouseEvent || 68 (e instanceof KeyboardEvent && 69 (e.code === 'Enter' || e.code === 'Space')) || 70 !e 71 ) { 72 this.statechart.send('LOAD_EVALUATION', { evaluation }); 73 } 74 } 75 76 @action 77 notifyEvalChange([evaluation]) { 78 schedule('actions', this, () => { 79 this.statechart.send('CHANGE_EVAL', { evaluation }); 80 }); 81 } 82 83 @action 84 updateEvaluationQueryParameter(context, { evaluation }) { 85 this.currentEval = evaluation.id; 86 } 87 88 @action 89 removeCurrentEvaluationQueryParameter() { 90 this.currentEval = null; 91 } 92 93 get shouldDisableNext() { 94 return !this.model.meta?.nextToken; 95 } 96 97 get shouldDisablePrev() { 98 return !this.previousTokens.length; 99 } 100 101 get optionsEvaluationsStatus() { 102 return [ 103 { key: null, label: 'All' }, 104 { key: 'blocked', label: 'Blocked' }, 105 { key: 'pending', label: 'Pending' }, 106 { key: 'complete', label: 'Complete' }, 107 { key: 'failed', label: 'Failed' }, 108 { key: 'canceled', label: 'Canceled' }, 109 ]; 110 } 111 112 get optionsTriggeredBy() { 113 return [ 114 { key: null, label: 'All' }, 115 { key: 'job-register', label: 'Job Register' }, 116 { key: 'job-deregister', label: 'Job Deregister' }, 117 { key: 'periodic-job', label: 'Periodic Job' }, 118 { key: 'node-drain', label: 'Node Drain' }, 119 { key: 'node-update', label: 'Node Update' }, 120 { key: 'alloc-stop', label: 'Allocation Stop' }, 121 { key: 'scheduled', label: 'Scheduled' }, 122 { key: 'rolling-update', label: 'Rolling Update' }, 123 { key: 'deployment-watcher', label: 'Deployment Watcher' }, 124 { key: 'failed-follow-up', label: 'Failed Follow Up' }, 125 { key: 'max-disconnect-timeout', label: 'Max Disconnect Timeout' }, 126 { key: 'max-plan-attempts', label: 'Max Plan Attempts' }, 127 { key: 'alloc-failure', label: 'Allocation Failure' }, 128 { key: 'queued-allocs', label: 'Queued Allocations' }, 129 { key: 'preemption', label: 'Preemption' }, 130 { key: 'job-scaling', label: 'Job Scalling' }, 131 ]; 132 } 133 134 get optionsNamespaces() { 135 const namespaces = this.store.peekAll('namespace').map((namespace) => ({ 136 key: namespace.name, 137 label: namespace.name, 138 })); 139 140 // Create default namespace selection 141 namespaces.unshift({ 142 key: ALL_NAMESPACE_WILDCARD, 143 label: 'All (*)', 144 }); 145 146 return namespaces; 147 } 148 149 get optionsType() { 150 return [ 151 { key: null, label: 'All' }, 152 { key: 'client', label: 'Client' }, 153 { key: 'no client', label: 'No Client' }, 154 ]; 155 } 156 157 filters = ['status', 'qpNamespace', 'type', 'triggeredBy', 'searchTerm']; 158 159 get hasFiltersApplied() { 160 return this.filters.reduce((result, filter) => { 161 // By default we always set qpNamespace to the '*' wildcard 162 // We need to ensure that if namespace is the only filter, that we send the correct error message to the user 163 if (this[filter] && filter !== 'qpNamespace') { 164 result = true; 165 } 166 return result; 167 }, false); 168 } 169 170 get currentFilters() { 171 const result = []; 172 for (const filter of this.filters) { 173 const isNamespaceWildcard = 174 filter === 'qpNamespace' && this[filter] === '*'; 175 if (this[filter] && !isNamespaceWildcard) { 176 result.push({ [filter]: this[filter] }); 177 } 178 } 179 return result; 180 } 181 182 get noMatchText() { 183 let text = ''; 184 const cleanNames = { 185 status: 'Status', 186 qpNamespace: 'Namespace', 187 type: 'Type', 188 triggeredBy: 'Triggered By', 189 searchTerm: 'Search Term', 190 }; 191 if (this.hasFiltersApplied) { 192 for (let i = 0; i < this.currentFilters.length; i++) { 193 const filter = this.currentFilters[i]; 194 const [name] = Object.keys(filter); 195 const filterName = cleanNames[name]; 196 const filterValue = filter[name]; 197 if (this.currentFilters.length === 1) 198 return `${filterName}: ${filterValue}.`; 199 if (i !== 0 && i !== this.currentFilters.length - 1) 200 text = text.concat(`, ${filterName}: ${filterValue}`); 201 if (i === 0) text = text.concat(`${filterName}: ${filterValue}`); 202 if (i === this.currentFilters.length - 1) { 203 return text.concat(`, ${filterName}: ${filterValue}.`); 204 } 205 } 206 } 207 208 return text; 209 } 210 211 @tracked pageSize = this.userSettings.pageSize; 212 @tracked nextToken = null; 213 @tracked previousTokens = []; 214 @tracked status = null; 215 @tracked triggeredBy = null; 216 @tracked qpNamespace = ALL_NAMESPACE_WILDCARD; 217 @tracked type = null; 218 @tracked searchTerm = null; 219 220 @action 221 onChange(newPageSize) { 222 this.pageSize = newPageSize; 223 } 224 225 @action 226 onNext(nextToken) { 227 this.previousTokens = [...this.previousTokens, this.nextToken]; 228 this.nextToken = nextToken; 229 } 230 231 @action 232 onPrev() { 233 const lastToken = this.previousTokens.pop(); 234 this.previousTokens = [...this.previousTokens]; 235 this.nextToken = lastToken; 236 } 237 238 @action 239 refresh() { 240 const isDefaultParams = this.nextToken === null && this.status === null; 241 if (isDefaultParams) { 242 getOwner(this).lookup('route:evaluations.index').refresh(); 243 return; 244 } 245 246 this._resetTokens(); 247 this.status = null; 248 this.pageSize = this.userSettings.pageSize; 249 } 250 251 @action 252 setQueryParam(qp, selection) { 253 this._resetTokens(); 254 this[qp] = selection; 255 } 256 257 @action 258 toggle() { 259 this._resetTokens(); 260 this.shouldOnlyDisplayClientEvals = !this.shouldOnlyDisplayClientEvals; 261 } 262 263 @action 264 _resetTokens() { 265 this.nextToken = null; 266 this.previousTokens = []; 267 } 268 }