mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
Parse and Plan API and UI workflows
This commit is contained in:
@@ -59,6 +59,26 @@ export default Watchable.extend({
|
||||
const url = this.urlForFindRecord(job.get('id'), 'job');
|
||||
return this.ajax(url, 'DELETE');
|
||||
},
|
||||
|
||||
parse(spec) {
|
||||
const url = addToPath(this.urlForFindAll('job'), '/parse');
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
JobHCL: spec,
|
||||
Canonicalize: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
plan(job) {
|
||||
const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/plan');
|
||||
return this.ajax(url, 'POST', {
|
||||
data: {
|
||||
Job: job.get('_newDefinitionJSON'),
|
||||
Diff: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function associateNamespace(url, namespace) {
|
||||
|
||||
36
ui/app/controllers/jobs/run.js
Normal file
36
ui/app/controllers/jobs/run.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { task } from 'ember-concurrency';
|
||||
|
||||
export default Controller.extend({
|
||||
stage: computed('planOutput', function() {
|
||||
return this.get('planOutput') ? 'plan' : 'editor';
|
||||
}),
|
||||
|
||||
plan: task(function*() {
|
||||
this.cancel();
|
||||
|
||||
try {
|
||||
yield this.get('model').parse();
|
||||
} catch (err) {
|
||||
this.set('parseError', err);
|
||||
}
|
||||
|
||||
try {
|
||||
const planOutput = yield this.get('model').plan();
|
||||
console.log('Heyo!', planOutput);
|
||||
this.set('planOutput', planOutput);
|
||||
} catch (err) {
|
||||
this.set('planError', err);
|
||||
console.log('Uhoh', err);
|
||||
}
|
||||
}).drop(),
|
||||
|
||||
submit: task(function*() {}),
|
||||
|
||||
cancel() {
|
||||
this.set('planOutput', null);
|
||||
this.set('planError', null);
|
||||
this.set('parseError', null);
|
||||
},
|
||||
});
|
||||
@@ -4,6 +4,8 @@ import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
import { belongsTo, hasMany } from 'ember-data/relationships';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
import RSVP from 'rsvp';
|
||||
import { assert } from '@ember/debug';
|
||||
|
||||
const JOB_TYPES = ['service', 'batch', 'system'];
|
||||
|
||||
@@ -191,6 +193,41 @@ export default Model.extend({
|
||||
return this.store.adapterFor('job').stop(this);
|
||||
},
|
||||
|
||||
plan() {
|
||||
assert('A job must be parsed before planned', this.get('_newDefinitionJSON'));
|
||||
return this.store.adapterFor('job').plan(this);
|
||||
},
|
||||
|
||||
parse() {
|
||||
const definition = this.get('_newDefinition');
|
||||
let promise;
|
||||
|
||||
try {
|
||||
// If the definition is already JSON then it doesn't need to be parsed.
|
||||
const json = JSON.parse(definition);
|
||||
this.set('_newDefinitionJSON', definition);
|
||||
this.setIDByPayload(json);
|
||||
promise = RSVP.Resolve(definition);
|
||||
} catch (err) {
|
||||
// If the definition is invalid JSON, assume it is HCL. If it is invalid
|
||||
// in anyway, the parse endpoint will throw an error.
|
||||
promise = this.store
|
||||
.adapterFor('job')
|
||||
.parse(this.get('_newDefinition'))
|
||||
.then(response => {
|
||||
this.set('_newDefinitionJSON', response);
|
||||
this.setIDByPayload(response);
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
setIDByPayload(payload) {
|
||||
this.set('plainId', payload.Name);
|
||||
this.set('id', JSON.stringify([payload.Name, payload.Namespace || 'default']));
|
||||
},
|
||||
|
||||
statusClass: computed('status', function() {
|
||||
const classMap = {
|
||||
pending: 'is-pending',
|
||||
@@ -206,4 +243,13 @@ export default Model.extend({
|
||||
// Lazily decode the base64 encoded payload
|
||||
return window.atob(this.get('payload') || '');
|
||||
}),
|
||||
|
||||
// An arbitrary HCL or JSON string that is used by the serializer to plan
|
||||
// and run this job. Used for both new job models and saved job models.
|
||||
_newDefinition: attr('string'),
|
||||
|
||||
// The new definition may be HCL, in which case the API will need to parse the
|
||||
// spec first. In order to preserve both the original HCL and the parsed response
|
||||
// that will be submitted to the create job endpoint, another prop is necessary.
|
||||
_newDefinitionJSON: attr('string'),
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { inject as service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
store: service(),
|
||||
system: service(),
|
||||
|
||||
breadcrumbs: [
|
||||
{
|
||||
@@ -10,4 +11,16 @@ export default Route.extend({
|
||||
args: ['jobs.run'],
|
||||
},
|
||||
],
|
||||
|
||||
model() {
|
||||
return this.get('store').createRecord('job', {
|
||||
namespace: this.get('system.activeNamespace'),
|
||||
});
|
||||
},
|
||||
|
||||
resetController(controller, isExiting) {
|
||||
if (isExiting) {
|
||||
controller.get('model').deleteRecord();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ $dark-bright: lighten($dark, 15%);
|
||||
}
|
||||
|
||||
span.cm-comment {
|
||||
color: $grey-light;
|
||||
color: $grey;
|
||||
}
|
||||
|
||||
span.cm-string,
|
||||
|
||||
@@ -1,31 +1,58 @@
|
||||
<section class="section">
|
||||
<div class="notification is-info">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 class="title is-4">Run a Job</h3>
|
||||
<p>Paste or author HCL or JSON to submit to your cluster. A plan will be requested before the job is submitted.</p>
|
||||
</div>
|
||||
<div class="column is-centered is-minimum">
|
||||
<button class="button is-info">Okay</button>
|
||||
{{#if (eq stage "editor")}}
|
||||
<div class="notification is-info">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 class="title is-4">Run a Job</h3>
|
||||
<p>Paste or author HCL or JSON to submit to your cluster. A plan will be requested before the job is submitted.</p>
|
||||
</div>
|
||||
<div class="column is-centered is-minimum">
|
||||
<button class="button is-info">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Job Definition
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Job Definition
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
{{ivy-codemirror
|
||||
value=(or model._newDefinition jobSpec)
|
||||
valueUpdated=(action (mut model._newDefinition))
|
||||
options=(hash
|
||||
mode="javascript"
|
||||
theme="hashi"
|
||||
tabSize=2
|
||||
lineNumbers=true
|
||||
)}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
{{ivy-codemirror
|
||||
value=jobSpec
|
||||
options=(hash
|
||||
mode="javascript"
|
||||
theme="hashi"
|
||||
tabSize=2
|
||||
lineNumbers=true
|
||||
)}}
|
||||
<div class="content is-associative">
|
||||
<button class="button is-primary {{if plan.isRunning "is-loading"}}" onclick={{perform plan}}>Plan</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content is-associative">
|
||||
<button class="button is-primary">Plan</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq stage "plan")}}
|
||||
<div class="notification is-info">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h3 class="title is-4">Job Plan</h3>
|
||||
<p>This is the impact running this job will have on your cluster.</p>
|
||||
</div>
|
||||
<div class="column is-centered is-minimum">
|
||||
<button class="button is-info">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">Job Plan</div>
|
||||
<div class="boxed-section-body is-dark">
|
||||
{{job-diff diff=planOutput.Diff}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content is-associative">
|
||||
<button class="button is-primary {{if submit.isRunning "is-loading"}}" onclick={{perform submit}}>Submit</button>
|
||||
<button class="button is-light" onclick={{action cancel}}>Cancel</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user