diff --git a/.changelog/17752.txt b/.changelog/17752.txt new file mode 100644 index 000000000..227f74279 --- /dev/null +++ b/.changelog/17752.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Adds a Download as .nomad.hcl button to jobspec editing in the UI +``` diff --git a/ui/app/components/job-editor.js b/ui/app/components/job-editor.js index 980b9e9bb..c03b4666e 100644 --- a/ui/app/components/job-editor.js +++ b/ui/app/components/job-editor.js @@ -21,6 +21,7 @@ import { tracked } from '@glimmer/tracking'; export default class JobEditor extends Component { @service config; @service store; + @service notifications; @tracked error = null; @tracked planOutput = null; @@ -199,6 +200,42 @@ export default class JobEditor extends Component { reader.readAsText(file); } + /** + * Download the job's definition or specification as .nomad.hcl file locally + */ + @action + async handleSaveAsFile() { + try { + const blob = new Blob([this.args.job._newDefinition], { + type: 'text/plain', + }); + const url = window.URL.createObjectURL(blob); + const downloadAnchor = document.createElement('a'); + + downloadAnchor.href = url; + downloadAnchor.target = '_blank'; + downloadAnchor.rel = 'noopener noreferrer'; + downloadAnchor.download = 'jobspec.nomad.hcl'; + + downloadAnchor.click(); + downloadAnchor.remove(); + + window.URL.revokeObjectURL(url); + this.notifications.add({ + title: 'jobspec.nomad.hcl has been downloaded', + color: 'success', + icon: 'download', + }); + } catch (err) { + this.notifications.add({ + title: 'Error downloading file', + message: err.message, + color: 'critical', + sticky: true, + }); + } + } + /** * Get the definition or specification based on the view type. * @@ -253,6 +290,7 @@ export default class JobEditor extends Component { onPlan: this.plan, onReset: this.reset, onSaveAs: this.args.handleSaveAsTemplate, + onSaveFile: this.handleSaveAsFile, onSubmit: this.submit, onSelect: this.args.onSelect, onUpdate: this.updateCode, diff --git a/ui/app/styles/components/codemirror.scss b/ui/app/styles/components/codemirror.scss index ac752e16b..e2605bb68 100644 --- a/ui/app/styles/components/codemirror.scss +++ b/ui/app/styles/components/codemirror.scss @@ -140,6 +140,7 @@ header.run-job-header { grid-template-columns: 1fr auto; margin-bottom: 2rem; gap: 0 1rem; + align-items: end; & > h1 { grid-column: -1 / 1; } @@ -166,6 +167,10 @@ header.run-job-header { bottom: 0; background: white; padding: 0.5rem 0; + + &.pull-left { + justify-content: flex-start; + } } } diff --git a/ui/app/templates/components/job-editor.hbs b/ui/app/templates/components/job-editor.hbs index 7b3498a65..cb22d2f8f 100644 --- a/ui/app/templates/components/job-editor.hbs +++ b/ui/app/templates/components/job-editor.hbs @@ -15,6 +15,28 @@

Paste or author HCL or JSON to submit to your cluster, or select from a list of templates. A plan will be requested before the job is submitted. You can also attach a job spec by uploading a job file or dragging & dropping a file to the editor.

+ + {{#if (can "write variable" path="*" namespace="*")}} + + + + + {{/if}} + {{/if}} {{did-update this.setDefinitionOnModel this.definition}} diff --git a/ui/app/templates/components/job-editor/edit.hbs b/ui/app/templates/components/job-editor/edit.hbs index 2a42cb1eb..cdcab2af5 100644 --- a/ui/app/templates/components/job-editor/edit.hbs +++ b/ui/app/templates/components/job-editor/edit.hbs @@ -83,7 +83,7 @@ {{/if}} -
+ {{#if @data.job.isNew}} - - - {{#if (can "write variable" path="*" namespace="*")}} - - - {{/if}} - + {{/if}} -
\ No newline at end of file + + diff --git a/ui/tests/integration/components/job-editor-test.js b/ui/tests/integration/components/job-editor-test.js index 18d817f8a..0d084e498 100644 --- a/ui/tests/integration/components/job-editor-test.js +++ b/ui/tests/integration/components/job-editor-test.js @@ -85,6 +85,7 @@ module('Integration | Component | job-editor', function (hooks) { @job={{job}} @context={{context}} @onSubmit={{onSubmit}} + @handleSaveAsTemplate={{handleSaveAsTemplate}} /> `; @@ -92,6 +93,7 @@ module('Integration | Component | job-editor', function (hooks) { component.setProperties({ job, onSubmit: sinon.spy(), + handleSaveAsTemplate: sinon.spy(), context: 'new', }); await component.render(commonTemplate);