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