feat (ui): exec fase 3
This commit is contained in:
@@ -467,6 +467,14 @@ pre{
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.execTerminalHost{
|
||||
min-height: 48vh;
|
||||
max-height: 64vh;
|
||||
border: 1px solid var(--soft-line);
|
||||
border-radius: 12px;
|
||||
background: #0a0f1d;
|
||||
overflow: hidden;
|
||||
}
|
||||
.execOutput{
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
@@ -482,6 +490,10 @@ pre{
|
||||
max-height: 64vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.execTerminalHost .xterm,
|
||||
.execTerminalHost .xterm-viewport{
|
||||
height: 100%;
|
||||
}
|
||||
.execInputRow{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -404,6 +404,9 @@ let execTerminalState = {
|
||||
container: '',
|
||||
sessionId: '',
|
||||
source: null,
|
||||
term: null,
|
||||
fitAddon: null,
|
||||
useXterm: false,
|
||||
reconnectTimer: null,
|
||||
reconnectAttempts: 0,
|
||||
reconnectEnabled: false,
|
||||
@@ -433,6 +436,8 @@ function _execEls() {
|
||||
back: document.getElementById('execModalBack'),
|
||||
title: document.getElementById('execModalTitle'),
|
||||
output: document.getElementById('execTerminalOutput'),
|
||||
termHost: document.getElementById('execTerminalHost'),
|
||||
inputRow: document.getElementById('execInputRow'),
|
||||
bufferInfo: document.getElementById('execTerminalBufferInfo'),
|
||||
input: document.getElementById('execTerminalInput'),
|
||||
sendBtn: document.getElementById('execTerminalSendBtn'),
|
||||
@@ -447,12 +452,88 @@ function _execSetStatus(msg) {
|
||||
if (status) status.textContent = msg || '';
|
||||
}
|
||||
|
||||
function _execCanUseXterm() {
|
||||
return !!(window.Terminal && window.FitAddon && window.FitAddon.FitAddon);
|
||||
}
|
||||
|
||||
function _execSetTerminalUiMode(useXterm) {
|
||||
const { termHost, output, inputRow, rawBtn } = _execEls();
|
||||
if (termHost) termHost.style.display = useXterm ? 'block' : 'none';
|
||||
if (output) output.style.display = useXterm ? 'none' : 'block';
|
||||
if (inputRow) inputRow.style.display = useXterm ? 'none' : 'flex';
|
||||
if (rawBtn) rawBtn.style.display = useXterm ? 'none' : 'inline-flex';
|
||||
}
|
||||
|
||||
function _execDestroyTerminal() {
|
||||
if (execTerminalState.term) {
|
||||
try { execTerminalState.term.dispose(); } catch (_) {}
|
||||
}
|
||||
execTerminalState.term = null;
|
||||
execTerminalState.fitAddon = null;
|
||||
execTerminalState.useXterm = false;
|
||||
}
|
||||
|
||||
function _execInitTerminal() {
|
||||
const { termHost } = _execEls();
|
||||
if (!_execCanUseXterm() || !termHost) {
|
||||
_execDestroyTerminal();
|
||||
_execSetTerminalUiMode(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
_execDestroyTerminal();
|
||||
try {
|
||||
const fitAddon = new window.FitAddon.FitAddon();
|
||||
const term = new window.Terminal({
|
||||
cursorBlink: true,
|
||||
convertEol: false,
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||
fontSize: 13,
|
||||
scrollback: 5000,
|
||||
theme: {
|
||||
background: '#0a0f1d',
|
||||
foreground: '#d9e6ff',
|
||||
},
|
||||
});
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(termHost);
|
||||
fitAddon.fit();
|
||||
|
||||
term.onData((data) => {
|
||||
const payload = String(data || '');
|
||||
if (!payload) return;
|
||||
const p = payload.length > EXEC_PASTE_CHUNK_SIZE
|
||||
? _execEnqueueChunkedInput(payload)
|
||||
: _execEnqueueInput(payload);
|
||||
p.catch((e) => {
|
||||
_execAppendOutput(`\n[ERROR] ${e.message || e}\n`);
|
||||
_execSwitchToLineFallback('xterm input error');
|
||||
});
|
||||
});
|
||||
|
||||
execTerminalState.term = term;
|
||||
execTerminalState.fitAddon = fitAddon;
|
||||
execTerminalState.useXterm = true;
|
||||
_execSetTerminalUiMode(true);
|
||||
term.focus();
|
||||
return true;
|
||||
} catch (_) {
|
||||
_execDestroyTerminal();
|
||||
_execSetTerminalUiMode(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function _execConnectedStatus() {
|
||||
const sid = execTerminalState.sessionId || '';
|
||||
return sid ? `Connected (${sid})` : 'Connected';
|
||||
}
|
||||
|
||||
function _execAppendOutput(txt) {
|
||||
if (execTerminalState.useXterm && execTerminalState.term) {
|
||||
try { execTerminalState.term.write(String(txt || '')); } catch (_) {}
|
||||
return;
|
||||
}
|
||||
const { output } = _execEls();
|
||||
if (!output || !txt) return;
|
||||
|
||||
@@ -469,6 +550,9 @@ function _execAppendOutput(txt) {
|
||||
}
|
||||
|
||||
function _execClearOutput() {
|
||||
if (execTerminalState.useXterm && execTerminalState.term) {
|
||||
try { execTerminalState.term.reset(); } catch (_) {}
|
||||
}
|
||||
const { output } = _execEls();
|
||||
execTerminalState.outputText = '';
|
||||
execTerminalState.outputLineCount = 0;
|
||||
@@ -478,6 +562,7 @@ function _execClearOutput() {
|
||||
}
|
||||
|
||||
function _execUpdateBufferInfo() {
|
||||
if (execTerminalState.useXterm) return;
|
||||
const { bufferInfo } = _execEls();
|
||||
if (!bufferInfo) return;
|
||||
if (execTerminalState.outputTruncated) {
|
||||
@@ -611,10 +696,11 @@ function _execAttachStream(afterSeq = 0, wasReconnect = false) {
|
||||
}
|
||||
|
||||
function _execComputeSize() {
|
||||
const { output } = _execEls();
|
||||
if (!output) return { rows: 24, cols: 80 };
|
||||
const w = output.clientWidth || 640;
|
||||
const h = output.clientHeight || 360;
|
||||
const { output, termHost } = _execEls();
|
||||
const target = (execTerminalState.useXterm && termHost) ? termHost : output;
|
||||
if (!target) return { rows: 24, cols: 80 };
|
||||
const w = target.clientWidth || 640;
|
||||
const h = target.clientHeight || 360;
|
||||
const cols = Math.max(20, Math.floor(w / 8));
|
||||
const rows = Math.max(8, Math.floor(h / 18));
|
||||
return { rows, cols };
|
||||
@@ -623,6 +709,9 @@ function _execComputeSize() {
|
||||
async function _execResizeNow() {
|
||||
const sid = execTerminalState.sessionId;
|
||||
if (!sid) return;
|
||||
if (execTerminalState.useXterm && execTerminalState.fitAddon) {
|
||||
try { execTerminalState.fitAddon.fit(); } catch (_) {}
|
||||
}
|
||||
const { rows, cols } = _execComputeSize();
|
||||
try {
|
||||
await api(`/containers/exec/${encodeURIComponent(sid)}/resize`, 'POST', { rows, cols });
|
||||
@@ -654,6 +743,8 @@ function _execUpdateInputModeUi() {
|
||||
}
|
||||
|
||||
function _execSwitchToLineFallback(reason) {
|
||||
_execDestroyTerminal();
|
||||
_execSetTerminalUiMode(false);
|
||||
if (!execTerminalState.rawMode) return;
|
||||
execTerminalState.rawMode = false;
|
||||
_execUpdateInputModeUi();
|
||||
@@ -799,6 +890,7 @@ async function containerExecOpen(name) {
|
||||
execTerminalState.reconnectEnabled = false;
|
||||
_execCancelReconnect();
|
||||
execTerminalState.rawMode = true;
|
||||
_execInitTerminal();
|
||||
_execUpdateInputModeUi();
|
||||
_execResetInputQueue();
|
||||
_execCloseEventSource();
|
||||
@@ -829,7 +921,11 @@ async function containerExecOpen(name) {
|
||||
execTerminalState.resizeObserver.observe(els.modal);
|
||||
}
|
||||
|
||||
if (els.input) els.input.focus();
|
||||
if (execTerminalState.useXterm && execTerminalState.term) {
|
||||
execTerminalState.term.focus();
|
||||
} else if (els.input) {
|
||||
els.input.focus();
|
||||
}
|
||||
} catch (e) {
|
||||
_execSetStatus('Start failed');
|
||||
_execAppendOutput(`\n[ERROR] ${e.message || e}\n`);
|
||||
@@ -890,6 +986,7 @@ function containerExecHandleInputBlur() {
|
||||
}
|
||||
|
||||
function containerExecToggleRawMode() {
|
||||
if (execTerminalState.useXterm) return;
|
||||
execTerminalState.rawMode = !execTerminalState.rawMode;
|
||||
_execUpdateInputModeUi();
|
||||
_execFocusInputSoon(0);
|
||||
@@ -902,6 +999,8 @@ async function containerExecClose() {
|
||||
execTerminalState.reconnectEnabled = false;
|
||||
_execCancelReconnect();
|
||||
_execCloseEventSource();
|
||||
_execDestroyTerminal();
|
||||
_execSetTerminalUiMode(false);
|
||||
if (execTerminalState.resizeObserver) {
|
||||
try { execTerminalState.resizeObserver.disconnect(); } catch (_) {}
|
||||
execTerminalState.resizeObserver = null;
|
||||
|
||||
Reference in New Issue
Block a user