github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/clients/index.js (about) 1 /* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */ 2 import { alias, readOnly } from '@ember/object/computed'; 3 import { inject as service } from '@ember/service'; 4 import Controller, { inject as controller } from '@ember/controller'; 5 import { action, computed } from '@ember/object'; 6 import { scheduleOnce } from '@ember/runloop'; 7 import intersection from 'lodash.intersection'; 8 import SortableFactory from 'nomad-ui/mixins/sortable-factory'; 9 import Searchable from 'nomad-ui/mixins/searchable'; 10 import { 11 serialize, 12 deserializedQueryParam as selection, 13 } from 'nomad-ui/utils/qp-serialize'; 14 import classic from 'ember-classic-decorator'; 15 16 @classic 17 export default class IndexController extends Controller.extend( 18 SortableFactory(['id', 'name', 'compositeStatus', 'datacenter', 'version']), 19 Searchable 20 ) { 21 @service userSettings; 22 @controller('clients') clientsController; 23 24 @alias('model.nodes') nodes; 25 @alias('model.agents') agents; 26 27 queryParams = [ 28 { 29 currentPage: 'page', 30 }, 31 { 32 searchTerm: 'search', 33 }, 34 { 35 sortProperty: 'sort', 36 }, 37 { 38 sortDescending: 'desc', 39 }, 40 { 41 qpClass: 'class', 42 }, 43 { 44 qpState: 'state', 45 }, 46 { 47 qpDatacenter: 'dc', 48 }, 49 { 50 qpVersion: 'version', 51 }, 52 { 53 qpVolume: 'volume', 54 }, 55 ]; 56 57 currentPage = 1; 58 @readOnly('userSettings.pageSize') pageSize; 59 60 sortProperty = 'modifyIndex'; 61 sortDescending = true; 62 63 @computed 64 get searchProps() { 65 return ['id', 'name', 'datacenter']; 66 } 67 68 qpClass = ''; 69 qpState = ''; 70 qpDatacenter = ''; 71 qpVersion = ''; 72 qpVolume = ''; 73 74 @selection('qpClass') selectionClass; 75 @selection('qpState') selectionState; 76 @selection('qpDatacenter') selectionDatacenter; 77 @selection('qpVersion') selectionVersion; 78 @selection('qpVolume') selectionVolume; 79 80 @computed('nodes.[]', 'selectionClass') 81 get optionsClass() { 82 const classes = Array.from(new Set(this.nodes.mapBy('nodeClass'))) 83 .compact() 84 .without(''); 85 86 // Remove any invalid node classes from the query param/selection 87 scheduleOnce('actions', () => { 88 // eslint-disable-next-line ember/no-side-effects 89 this.set( 90 'qpClass', 91 serialize(intersection(classes, this.selectionClass)) 92 ); 93 }); 94 95 return classes.sort().map((dc) => ({ key: dc, label: dc })); 96 } 97 98 @computed 99 get optionsState() { 100 return [ 101 { key: 'initializing', label: 'Initializing' }, 102 { key: 'ready', label: 'Ready' }, 103 { key: 'down', label: 'Down' }, 104 { key: 'ineligible', label: 'Ineligible' }, 105 { key: 'draining', label: 'Draining' }, 106 { key: 'disconnected', label: 'Disconnected' }, 107 ]; 108 } 109 110 @computed('nodes.[]', 'selectionDatacenter') 111 get optionsDatacenter() { 112 const datacenters = Array.from( 113 new Set(this.nodes.mapBy('datacenter')) 114 ).compact(); 115 116 // Remove any invalid datacenters from the query param/selection 117 scheduleOnce('actions', () => { 118 // eslint-disable-next-line ember/no-side-effects 119 this.set( 120 'qpDatacenter', 121 serialize(intersection(datacenters, this.selectionDatacenter)) 122 ); 123 }); 124 125 return datacenters.sort().map((dc) => ({ key: dc, label: dc })); 126 } 127 128 @computed('nodes.[]', 'selectionVersion') 129 get optionsVersion() { 130 const versions = Array.from(new Set(this.nodes.mapBy('version'))).compact(); 131 132 // Remove any invalid versions from the query param/selection 133 scheduleOnce('actions', () => { 134 // eslint-disable-next-line ember/no-side-effects 135 this.set( 136 'qpVersion', 137 serialize(intersection(versions, this.selectionVersion)) 138 ); 139 }); 140 141 return versions.sort().map((v) => ({ key: v, label: v })); 142 } 143 144 @computed('nodes.[]', 'selectionVolume') 145 get optionsVolume() { 146 const flatten = (acc, val) => acc.concat(val.toArray()); 147 148 const allVolumes = this.nodes.mapBy('hostVolumes').reduce(flatten, []); 149 const volumes = Array.from(new Set(allVolumes.mapBy('name'))); 150 151 scheduleOnce('actions', () => { 152 // eslint-disable-next-line ember/no-side-effects 153 this.set( 154 'qpVolume', 155 serialize(intersection(volumes, this.selectionVolume)) 156 ); 157 }); 158 159 return volumes.sort().map((volume) => ({ key: volume, label: volume })); 160 } 161 162 @computed( 163 'nodes.[]', 164 'selectionClass', 165 'selectionState', 166 'selectionDatacenter', 167 'selectionVersion', 168 'selectionVolume' 169 ) 170 get filteredNodes() { 171 const { 172 selectionClass: classes, 173 selectionState: states, 174 selectionDatacenter: datacenters, 175 selectionVersion: versions, 176 selectionVolume: volumes, 177 } = this; 178 179 const onlyIneligible = states.includes('ineligible'); 180 const onlyDraining = states.includes('draining'); 181 182 // states is a composite of node status and other node states 183 const statuses = states.without('ineligible').without('draining'); 184 185 return this.nodes.filter((node) => { 186 if (classes.length && !classes.includes(node.get('nodeClass'))) 187 return false; 188 if (statuses.length && !statuses.includes(node.get('status'))) 189 return false; 190 if (datacenters.length && !datacenters.includes(node.get('datacenter'))) 191 return false; 192 if (versions.length && !versions.includes(node.get('version'))) 193 return false; 194 if ( 195 volumes.length && 196 !node.hostVolumes.find((volume) => volumes.includes(volume.name)) 197 ) 198 return false; 199 200 if (onlyIneligible && node.get('isEligible')) return false; 201 if (onlyDraining && !node.get('isDraining')) return false; 202 203 return true; 204 }); 205 } 206 207 @alias('filteredNodes') listToSort; 208 @alias('listSorted') listToSearch; 209 @alias('listSearched') sortedNodes; 210 211 @alias('clientsController.isForbidden') isForbidden; 212 213 setFacetQueryParam(queryParam, selection) { 214 this.set(queryParam, serialize(selection)); 215 } 216 217 @action 218 gotoNode(node) { 219 this.transitionToRoute('clients.client', node); 220 } 221 }