/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import { inject as service } from '@ember/service'; import Controller from '@ember/controller'; import { action, computed } from '@ember/object'; import { alias, mapBy, sort, uniq } from '@ember/object/computed'; import escapeTaskName from 'nomad-ui/utils/escape-task-name'; import ExecCommandEditorXtermAdapter from 'nomad-ui/utils/classes/exec-command-editor-xterm-adapter'; import ExecSocketXtermAdapter from 'nomad-ui/utils/classes/exec-socket-xterm-adapter'; import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; import classic from 'ember-classic-decorator'; const ANSI_UI_GRAY_400 = '\x1b[38;2;142;150;163m'; const ANSI_WHITE = '\x1b[0m'; @classic export default class ExecController extends Controller { @service sockets; @service system; @service token; queryParams = ['allocation', 'namespace']; @localStorageProperty('nomadExecCommand', '/bin/bash') command; socketOpen = false; @computed('model.allocations.@each.clientStatus') get pendingAndRunningAllocations() { return this.model.allocations.filter( (allocation) => allocation.clientStatus === 'pending' || allocation.clientStatus === 'running' ); } @mapBy('pendingAndRunningAllocations', 'taskGroup') pendingAndRunningTaskGroups; @uniq('pendingAndRunningTaskGroups') uniquePendingAndRunningTaskGroups; taskGroupSorting = ['name']; @sort('uniquePendingAndRunningTaskGroups', 'taskGroupSorting') sortedTaskGroups; setUpTerminal(Terminal) { this.terminal = new Terminal({ fontFamily: 'monospace', fontWeight: '400', }); window.execTerminal = this.terminal; // Issue to improve: https://github.com/hashicorp/nomad/issues/7457 this.terminal.write(ANSI_UI_GRAY_400); if (this.sortedTaskGroups.length > 0) { this.terminal.writeln('Select a task to start your session.'); } else { this.terminal.writeln(`There are no tasks running for this job.`); } } @alias('model.allocations') allocations; @computed( 'allocations.{[],@each.isActive}', 'allocationShortId', 'taskName', 'taskGroupName', 'allocation', 'allocation.states.@each.{name,isRunning}' ) get taskState() { if (!this.allocations) { return false; } let allocation; if (this.allocationShortId) { allocation = this.allocations.findBy('shortId', this.allocationShortId); } else { allocation = this.allocations.find((allocation) => allocation.states .filterBy('isActive') .mapBy('name') .includes(this.taskName) ); } if (allocation) { return allocation.states.find((state) => state.name === this.taskName); } return undefined; } @action setTaskProperties({ allocationShortId, taskName, taskGroupName }) { this.setProperties({ allocationShortId, taskName, taskGroupName, }); if (this.taskState) { this.terminal.write(ANSI_UI_GRAY_400); this.terminal.writeln(''); if (!allocationShortId) { this.terminal.writeln( 'Multiple instances of this task are running. The allocation below was selected by random draw.' ); this.terminal.writeln(''); } this.terminal.writeln( 'Customize your command, then hit ‘return’ to run.' ); this.terminal.writeln(''); this.terminal.write( `$ nomad alloc exec -i -t -task ${escapeTaskName(taskName)} ${ this.taskState.allocation.shortId } ` ); this.terminal.write(ANSI_WHITE); this.terminal.write(this.command); if (this.commandEditorAdapter) { this.commandEditorAdapter.destroy(); } this.commandEditorAdapter = new ExecCommandEditorXtermAdapter( this.terminal, this.openAndConnectSocket.bind(this), this.command ); } } openAndConnectSocket(command) { if (this.taskState) { this.set('socketOpen', true); this.set('command', command); this.socket = this.sockets.getTaskStateSocket(this.taskState, command); new ExecSocketXtermAdapter(this.terminal, this.socket, this.token.secret); } else { this.terminal.writeln( `Failed to open a socket because task ${this.taskName} is not active.` ); } } }