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