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