/** * javascript/terminal.js * * Terminal panel: one-command-at-a-time exec via ajax/exec.php. See that * file's header comment for the full, honest explanation of why this is * "type a command, see its output" rather than a true interactive PTY — * the short version is that libpod's interactive exec needs a persistent * bidirectional connection this PHP/AJAX stack doesn't have, and faking * interactivity on top of that would break the moment a user ran anything * that expects a real terminal (vim, an interactive prompt, etc). * * `cd` is handled client-side: this module tracks a per-session `cwd` and * passes it as the exec's working directory on every call, so at least * directory navigation feels persistent even though nothing else is. */ (function () { 'use strict'; const P = window.Podman; let cwd = '/'; let containerId = null; function appendLine(html) { const out = P.el('term-output'); const div = document.createElement('div'); div.innerHTML = html; out.appendChild(div); out.scrollTop = out.scrollHeight; } function promptHtml() { return 'root:' + P.escapeHtml(cwd) + '$'; } function runCommand(cmd) { appendLine(promptHtml() + ' ' + P.escapeHtml(cmd)); // `cd ` is intercepted client-side (see file header) rather than // sent as a real command, since a one-shot exec has no way to report // "the working directory changed" back to us otherwise. const cdMatch = cmd.trim().match(/^cd\s+(\S+)$/); if (cdMatch) { cwd = cdMatch[1].startsWith('/') ? cdMatch[1] : (cwd.replace(/\/$/, '') + '/' + cdMatch[1]); return Promise.resolve(); } return P.post('exec', 'run', { id: containerId, cmd: cmd, cwd: cwd }).then(function (data) { if (data.output) appendLine('' + P.escapeHtml(data.output).replace(/\n/g, '
') + '
'); }).catch(function (err) { appendLine('' + P.escapeHtml(err.message) + ''); }); } function populateContainerSelect(containers) { const select = P.el('term-container-select'); select.innerHTML = containers .filter(function (c) { return c.state === 'running'; }) .map(function (c) { return ''; }) .join(''); containerId = select.value || null; } function init() { const input = P.el('term-input'); P.el('term-container-select').addEventListener('change', function (e) { containerId = e.target.value; cwd = '/'; P.el('term-output').innerHTML = ''; }); input.addEventListener('keydown', function (e) { if (e.key !== 'Enter') return; const cmd = input.value; input.value = ''; if (!containerId) { appendLine('No running container selected.'); return; } if (cmd.trim() === '') return; runCommand(cmd); }); return P.get('containers', 'list').then(populateContainerSelect).catch(function (err) { appendLine('' + P.escapeHtml(err.message) + ''); }); } P.registerPanel('terminal', { init: init }); })();