github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/jobs/job/clients.js (about) 1 /* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */ 2 import Controller from '@ember/controller'; 3 import { action, computed } from '@ember/object'; 4 import { scheduleOnce } from '@ember/runloop'; 5 import intersection from 'lodash.intersection'; 6 import { alias } from '@ember/object/computed'; 7 import SortableFactory from 'nomad-ui/mixins/sortable-factory'; 8 import Searchable from 'nomad-ui/mixins/searchable'; 9 import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting'; 10 import jobClientStatus from 'nomad-ui/utils/properties/job-client-status'; 11 import { 12 serialize, 13 deserializedQueryParam as selection, 14 } from 'nomad-ui/utils/qp-serialize'; 15 import classic from 'ember-classic-decorator'; 16 import { inject as service } from '@ember/service'; 17 18 @classic 19 export default class ClientsController extends Controller.extend( 20 SortableFactory(['id', 'name', 'jobStatus']), 21 Searchable, 22 WithNamespaceResetting 23 ) { 24 @service store; 25 26 queryParams = [ 27 { 28 currentPage: 'page', 29 }, 30 { 31 searchTerm: 'search', 32 }, 33 { 34 qpStatus: 'status', 35 }, 36 { 37 qpDatacenter: 'dc', 38 }, 39 { 40 qpClientClass: 'clientclass', 41 }, 42 { 43 sortProperty: 'sort', 44 }, 45 { 46 sortDescending: 'desc', 47 }, 48 ]; 49 50 qpStatus = ''; 51 qpDatacenter = ''; 52 qpClientClass = ''; 53 54 currentPage = 1; 55 pageSize = 25; 56 57 sortProperty = 'jobStatus'; 58 sortDescending = false; 59 60 @selection('qpStatus') selectionStatus; 61 @selection('qpDatacenter') selectionDatacenter; 62 @selection('qpClientClass') selectionClientClass; 63 64 @alias('model') job; 65 @jobClientStatus('allNodes', 'job') jobClientStatus; 66 67 @alias('filteredNodes') listToSort; 68 @alias('listSorted') listToSearch; 69 @alias('listSearched') sortedClients; 70 71 @computed('store') 72 get allNodes() { 73 return this.store.peekAll('node'); 74 } 75 76 @computed('allNodes', 'jobClientStatus.byNode') 77 get nodes() { 78 return this.allNodes.filter((node) => this.jobClientStatus.byNode[node.id]); 79 } 80 81 @computed 82 get searchProps() { 83 return ['node.id', 'node.name']; 84 } 85 86 @computed( 87 'nodes', 88 'job.allocations', 89 'jobClientStatus.byNode', 90 'selectionStatus', 91 'selectionDatacenter', 92 'selectionClientClass' 93 ) 94 get filteredNodes() { 95 const { 96 selectionStatus: statuses, 97 selectionDatacenter: datacenters, 98 selectionClientClass: clientClasses, 99 } = this; 100 101 return this.nodes 102 .filter((node) => { 103 if ( 104 statuses.length && 105 !statuses.includes(this.jobClientStatus.byNode[node.id]) 106 ) { 107 return false; 108 } 109 if (datacenters.length && !datacenters.includes(node.datacenter)) { 110 return false; 111 } 112 if (clientClasses.length && !clientClasses.includes(node.nodeClass)) { 113 return false; 114 } 115 116 return true; 117 }) 118 .map((node) => { 119 const allocations = this.job.allocations.filter( 120 (alloc) => alloc.get('node.id') == node.id 121 ); 122 123 return { 124 node, 125 jobStatus: this.jobClientStatus.byNode[node.id], 126 allocations, 127 createTime: eldestCreateTime(allocations), 128 modifyTime: mostRecentModifyTime(allocations), 129 }; 130 }); 131 } 132 133 @computed 134 get optionsJobStatus() { 135 return [ 136 { key: 'queued', label: 'Queued' }, 137 { key: 'notScheduled', label: 'Not Scheduled' }, 138 { key: 'starting', label: 'Starting' }, 139 { key: 'running', label: 'Running' }, 140 { key: 'complete', label: 'Complete' }, 141 { key: 'degraded', label: 'Degraded' }, 142 { key: 'failed', label: 'Failed' }, 143 { key: 'lost', label: 'Lost' }, 144 { key: 'unknown', label: 'Unknown' }, 145 ]; 146 } 147 148 @computed('selectionDatacenter', 'nodes') 149 get optionsDatacenter() { 150 const datacenters = Array.from( 151 new Set(this.nodes.mapBy('datacenter')) 152 ).compact(); 153 154 // Update query param when the list of datacenters changes. 155 scheduleOnce('actions', () => { 156 // eslint-disable-next-line ember/no-side-effects 157 this.set( 158 'qpDatacenter', 159 serialize(intersection(datacenters, this.selectionDatacenter)) 160 ); 161 }); 162 163 return datacenters.sort().map((dc) => ({ key: dc, label: dc })); 164 } 165 166 @computed('selectionClientClass', 'nodes') 167 get optionsClientClass() { 168 const clientClasses = Array.from( 169 new Set(this.nodes.mapBy('nodeClass')) 170 ).compact(); 171 172 // Update query param when the list of datacenters changes. 173 scheduleOnce('actions', () => { 174 // eslint-disable-next-line ember/no-side-effects 175 this.set( 176 'qpClientClass', 177 serialize(intersection(clientClasses, this.selectionClientClass)) 178 ); 179 }); 180 181 return clientClasses 182 .sort() 183 .map((clientClass) => ({ key: clientClass, label: clientClass })); 184 } 185 186 @action 187 gotoClient(client) { 188 this.transitionToRoute('clients.client', client); 189 } 190 191 setFacetQueryParam(queryParam, selection) { 192 this.set(queryParam, serialize(selection)); 193 } 194 } 195 196 function eldestCreateTime(allocations) { 197 let eldest = null; 198 for (const alloc of allocations) { 199 if (!eldest || alloc.createTime < eldest) { 200 eldest = alloc.createTime; 201 } 202 } 203 return eldest; 204 } 205 206 function mostRecentModifyTime(allocations) { 207 let mostRecent = null; 208 for (const alloc of allocations) { 209 if (!mostRecent || alloc.modifyTime > mostRecent) { 210 mostRecent = alloc.modifyTime; 211 } 212 } 213 return mostRecent; 214 }