github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/components/job-editor.js (about) 1 import Component from '@ember/component'; 2 import { assert } from '@ember/debug'; 3 import { inject as service } from '@ember/service'; 4 import { computed, action } from '@ember/object'; 5 import { task } from 'ember-concurrency'; 6 import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; 7 import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; 8 import { attributeBindings } from '@ember-decorators/component'; 9 import classic from 'ember-classic-decorator'; 10 11 @classic 12 @attributeBindings('data-test-job-editor') 13 export default class JobEditor extends Component { 14 @service store; 15 @service config; 16 17 'data-test-job-editor' = true; 18 19 job = null; 20 onSubmit() {} 21 22 @computed('_context') 23 get context() { 24 return this._context; 25 } 26 27 set context(value) { 28 const allowedValues = ['new', 'edit']; 29 30 assert( 31 `context must be one of: ${allowedValues.join(', ')}`, 32 allowedValues.includes(value) 33 ); 34 35 this.set('_context', value); 36 } 37 38 @action updateCode(value) { 39 if (!this.job.isDestroying && !this.job.isDestroyed) { 40 this.job.set('_newDefinition', value); 41 } 42 } 43 44 _context = null; 45 parseError = null; 46 planError = null; 47 runError = null; 48 49 planOutput = null; 50 51 @localStorageProperty('nomadMessageJobPlan', true) showPlanMessage; 52 53 @computed('planOutput') 54 get stage() { 55 return this.planOutput ? 'plan' : 'editor'; 56 } 57 58 @(task(function* () { 59 this.reset(); 60 61 try { 62 yield this.job.parse(); 63 } catch (err) { 64 const error = 65 messageFromAdapterError(err, 'parse jobs') || 'Could not parse input'; 66 this.set('parseError', error); 67 this.scrollToError(); 68 return; 69 } 70 71 try { 72 const plan = yield this.job.plan(); 73 this.set('planOutput', plan); 74 } catch (err) { 75 const error = 76 messageFromAdapterError(err, 'plan jobs') || 'Could not plan job'; 77 this.set('planError', error); 78 this.scrollToError(); 79 } 80 }).drop()) 81 plan; 82 83 @task(function* () { 84 try { 85 if (this.context === 'new') { 86 yield this.job.run(); 87 } else { 88 yield this.job.update(); 89 } 90 91 const id = this.get('job.plainId'); 92 const namespace = this.get('job.namespace.name') || 'default'; 93 94 this.reset(); 95 96 // Treat the job as ephemeral and only provide ID parts. 97 this.onSubmit(id, namespace); 98 } catch (err) { 99 const error = messageFromAdapterError(err) || 'Could not submit job'; 100 this.set('runError', error); 101 this.set('planOutput', null); 102 this.scrollToError(); 103 } 104 }) 105 submit; 106 107 reset() { 108 this.set('planOutput', null); 109 this.set('planError', null); 110 this.set('parseError', null); 111 this.set('runError', null); 112 } 113 114 scrollToError() { 115 if (!this.get('config.isTest')) { 116 window.scrollTo(0, 0); 117 } 118 } 119 120 @action uploadJobSpec(event) { 121 const reader = new FileReader(); 122 reader.onload = () => { 123 this.updateCode(reader.result); 124 }; 125 126 const [file] = event.target.files; 127 reader.readAsText(file); 128 } 129 }