github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/components/job-dispatch.js (about) 1 import Component from '@glimmer/component'; 2 import { tracked } from '@glimmer/tracking'; 3 import { inject as service } from '@ember/service'; 4 import { action } from '@ember/object'; 5 import { A } from '@ember/array'; 6 import { task } from 'ember-concurrency'; 7 import { noCase } from 'no-case'; 8 import { titleCase } from 'title-case'; 9 import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; 10 11 class MetaField { 12 @tracked value; 13 @tracked error; 14 15 name; 16 required; 17 title; 18 19 constructor(meta) { 20 this.name = meta.name; 21 this.required = meta.required; 22 this.title = meta.title; 23 this.value = meta.value; 24 this.error = meta.error; 25 } 26 27 validate() { 28 this.error = ''; 29 30 if (this.required && !this.value) { 31 this.error = `Missing required meta parameter "${this.name}".`; 32 } 33 } 34 } 35 36 export default class JobDispatch extends Component { 37 @service router; 38 @service config; 39 40 @tracked metaFields = []; 41 @tracked payload = ''; 42 @tracked payloadHasError = false; 43 44 errors = A([]); 45 46 constructor() { 47 super(...arguments); 48 49 // Helper for mapping the params into a useable form. 50 const mapper = (values, required) => 51 values.map( 52 (x) => 53 new MetaField({ 54 name: x, 55 required, 56 title: titleCase(noCase(x)), 57 value: this.args.job.meta ? this.args.job.meta.get(x) : '', 58 }) 59 ); 60 61 // Fetch the different types of parameters. 62 const required = mapper( 63 this.args.job.parameterizedDetails.MetaRequired || [], 64 true 65 ); 66 const optional = mapper( 67 this.args.job.parameterizedDetails.MetaOptional || [], 68 false 69 ); 70 71 // Merge them, required before optional. 72 this.metaFields = required.concat(optional); 73 } 74 75 get hasPayload() { 76 return this.args.job.parameterizedDetails.Payload !== 'forbidden'; 77 } 78 79 get payloadRequired() { 80 return this.args.job.parameterizedDetails.Payload === 'required'; 81 } 82 83 @action 84 dispatch() { 85 this.validateForm(); 86 if (this.errors.length > 0) { 87 this.scrollToError(); 88 return; 89 } 90 91 this.onDispatched.perform(); 92 } 93 94 @action 95 cancel() { 96 this.router.transitionTo('jobs.job'); 97 } 98 99 @task({ drop: true }) *onDispatched() { 100 // Try to create the dispatch. 101 try { 102 let paramValues = {}; 103 this.metaFields.forEach((m) => (paramValues[m.name] = m.value)); 104 const dispatch = yield this.args.job.dispatch(paramValues, this.payload); 105 106 // Navigate to the newly created instance. 107 const namespaceId = this.args.job.belongsTo('namespace').id(); 108 const jobId = namespaceId 109 ? `${dispatch.DispatchedJobID}@${namespaceId}` 110 : dispatch.DispatchedJobID; 111 112 this.router.transitionTo('jobs.job', jobId); 113 } catch (err) { 114 const error = messageFromAdapterError(err) || 'Could not dispatch job'; 115 this.errors.pushObject(error); 116 this.scrollToError(); 117 } 118 } 119 120 scrollToError() { 121 if (!this.config.isTest) { 122 window.scrollTo(0, 0); 123 } 124 } 125 126 resetErrors() { 127 this.payloadHasError = false; 128 this.errors.clear(); 129 } 130 131 validateForm() { 132 this.resetErrors(); 133 134 // Make sure that we have all of the meta fields that we need. 135 this.metaFields.forEach((f) => { 136 f.validate(); 137 if (f.error) { 138 this.errors.pushObject(f.error); 139 } 140 }); 141 142 // Validate payload. 143 if (this.payloadRequired && !this.payload) { 144 this.errors.pushObject('Missing required payload.'); 145 this.payloadHasError = true; 146 } 147 } 148 }