Adicionar timer e melhorar espaçamento da toolbar do terminal
- Contador de tempo (mm:ss ou hh:mm:ss) que inicia ao processar e persiste entre reloads via sessionStorage - Espaçamento maior entre elementos da toolbar principal e action bar - Timer para ao completar execução/pipeline ou ao limpar terminal
This commit is contained in:
@@ -486,6 +486,10 @@
|
|||||||
<span class="terminal-title">Output de Execução</span>
|
<span class="terminal-title">Output de Execução</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="terminal-toolbar-right">
|
<div class="terminal-toolbar-right">
|
||||||
|
<div class="terminal-timer" id="terminal-timer" hidden>
|
||||||
|
<i data-lucide="clock" style="width:14px;height:14px"></i>
|
||||||
|
<span id="terminal-timer-value">00:00</span>
|
||||||
|
</div>
|
||||||
<select class="select select--sm" id="terminal-execution-select" aria-label="Selecionar execução">
|
<select class="select select--sm" id="terminal-execution-select" aria-label="Selecionar execução">
|
||||||
<option value="">Selecionar execução...</option>
|
<option value="">Selecionar execução...</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -2681,25 +2681,39 @@ tbody tr:hover td {
|
|||||||
|
|
||||||
.terminal-toolbar {
|
.terminal-toolbar {
|
||||||
background-color: var(--bg-secondary);
|
background-color: var(--bg-secondary);
|
||||||
padding: 10px 16px;
|
padding: 12px 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 1px solid var(--border-primary);
|
border-bottom: 1px solid var(--border-primary);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-toolbar-left {
|
.terminal-toolbar-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-toolbar-right {
|
.terminal-toolbar-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-timer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--success);
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.terminal-dot--red {
|
.terminal-dot--red {
|
||||||
@@ -4489,13 +4503,15 @@ body, .sidebar, .header, .card, .modal-content, .input, .select, textarea, .metr
|
|||||||
|
|
||||||
.terminal-action-toolbar {
|
.terminal-action-toolbar {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 1rem;
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.terminal-toolbar-left, .terminal-toolbar-right { display: flex; align-items: center; gap: 0.25rem; }
|
.terminal-action-toolbar .terminal-toolbar-left,
|
||||||
|
.terminal-action-toolbar .terminal-toolbar-right { display: flex; align-items: center; gap: 0.5rem; }
|
||||||
.terminal-toggle-label {
|
.terminal-toggle-label {
|
||||||
display: flex; align-items: center; gap: 0.375rem;
|
display: flex; align-items: center; gap: 0.375rem;
|
||||||
font-size: 0.75rem; color: var(--text-secondary); cursor: pointer; user-select: none;
|
font-size: 0.75rem; color: var(--text-secondary); cursor: pointer; user-select: none;
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const App = {
|
|||||||
App._pipelineDropzone = Utils.initDropzone('pipeline-execute-dropzone', 'pipeline-execute-files', 'pipeline-execute-file-list');
|
App._pipelineDropzone = Utils.initDropzone('pipeline-execute-dropzone', 'pipeline-execute-files', 'pipeline-execute-file-list');
|
||||||
App._initRepoSelectors();
|
App._initRepoSelectors();
|
||||||
|
|
||||||
|
Terminal.restoreIfActive();
|
||||||
|
|
||||||
const initialSection = location.hash.replace('#', '') || 'dashboard';
|
const initialSection = location.hash.replace('#', '') || 'dashboard';
|
||||||
App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard');
|
App.navigateTo(App.sections.includes(initialSection) ? initialSection : 'dashboard');
|
||||||
App.startPeriodicRefresh();
|
App.startPeriodicRefresh();
|
||||||
@@ -235,6 +237,7 @@ const App = {
|
|||||||
Toast.success('Execução concluída');
|
Toast.success('Execução concluída');
|
||||||
App.refreshCurrentSection();
|
App.refreshCurrentSection();
|
||||||
App._updateActiveBadge();
|
App._updateActiveBadge();
|
||||||
|
App._checkStopTimer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +252,7 @@ const App = {
|
|||||||
|
|
||||||
Toast.error(`Erro na execução: ${data.data?.error || 'desconhecido'}`);
|
Toast.error(`Erro na execução: ${data.data?.error || 'desconhecido'}`);
|
||||||
App._updateActiveBadge();
|
App._updateActiveBadge();
|
||||||
|
App._checkStopTimer();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'execution_retry':
|
case 'execution_retry':
|
||||||
@@ -294,6 +298,7 @@ const App = {
|
|||||||
|
|
||||||
case 'pipeline_complete':
|
case 'pipeline_complete':
|
||||||
Terminal.stopProcessing();
|
Terminal.stopProcessing();
|
||||||
|
Terminal._hideTimer();
|
||||||
Terminal.addLine('Pipeline concluído com sucesso.', 'success');
|
Terminal.addLine('Pipeline concluído com sucesso.', 'success');
|
||||||
if (data.lastSessionId && data.lastAgentId) {
|
if (data.lastSessionId && data.lastAgentId) {
|
||||||
Terminal.enableChat(data.lastAgentId, data.lastAgentName || 'Agente', data.lastSessionId);
|
Terminal.enableChat(data.lastAgentId, data.lastAgentName || 'Agente', data.lastSessionId);
|
||||||
@@ -304,6 +309,7 @@ const App = {
|
|||||||
|
|
||||||
case 'pipeline_error':
|
case 'pipeline_error':
|
||||||
Terminal.stopProcessing();
|
Terminal.stopProcessing();
|
||||||
|
Terminal._hideTimer();
|
||||||
Terminal.addLine(`Erro no passo ${data.stepIndex + 1}: ${data.error}`, 'error');
|
Terminal.addLine(`Erro no passo ${data.stepIndex + 1}: ${data.error}`, 'error');
|
||||||
Toast.error('Erro no pipeline');
|
Toast.error('Erro no pipeline');
|
||||||
break;
|
break;
|
||||||
@@ -1074,6 +1080,15 @@ const App = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async _checkStopTimer() {
|
||||||
|
try {
|
||||||
|
const active = await API.system.activeExecutions();
|
||||||
|
if (!Array.isArray(active) || active.length === 0) {
|
||||||
|
Terminal._hideTimer();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
startPeriodicRefresh() {
|
startPeriodicRefresh() {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await App._updateActiveBadge();
|
await App._updateActiveBadge();
|
||||||
|
|||||||
@@ -8,9 +8,60 @@ const Terminal = {
|
|||||||
searchMatches: [],
|
searchMatches: [],
|
||||||
searchIndex: -1,
|
searchIndex: -1,
|
||||||
_toolbarInitialized: false,
|
_toolbarInitialized: false,
|
||||||
|
_storageKey: 'terminal_lines',
|
||||||
|
_chatStorageKey: 'terminal_chat',
|
||||||
|
_timerInterval: null,
|
||||||
|
_timerStart: null,
|
||||||
|
_timerStorageKey: 'terminal_timer_start',
|
||||||
|
|
||||||
|
_saveToStorage() {
|
||||||
|
try {
|
||||||
|
const data = JSON.stringify(Terminal.lines.slice(-Terminal.maxLines));
|
||||||
|
sessionStorage.setItem(Terminal._storageKey, data);
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
_restoreFromStorage() {
|
||||||
|
try {
|
||||||
|
const data = sessionStorage.getItem(Terminal._storageKey);
|
||||||
|
if (data) {
|
||||||
|
Terminal.lines = JSON.parse(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_clearStorage() {
|
||||||
|
try {
|
||||||
|
sessionStorage.removeItem(Terminal._storageKey);
|
||||||
|
sessionStorage.removeItem(Terminal._chatStorageKey);
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
async restoreIfActive() {
|
||||||
|
try {
|
||||||
|
const active = await API.system.activeExecutions();
|
||||||
|
const hasActive = Array.isArray(active) && active.length > 0;
|
||||||
|
if (hasActive && Terminal._restoreFromStorage()) {
|
||||||
|
Terminal.render();
|
||||||
|
const savedStart = sessionStorage.getItem(Terminal._timerStorageKey);
|
||||||
|
Terminal._startTimer(savedStart ? Number(savedStart) : null);
|
||||||
|
Terminal.startProcessing(active[0].agentConfig?.agent_name || 'Agente');
|
||||||
|
try {
|
||||||
|
const chatData = sessionStorage.getItem(Terminal._chatStorageKey);
|
||||||
|
if (chatData) Terminal._chatSession = JSON.parse(chatData);
|
||||||
|
} catch {}
|
||||||
|
} else if (!hasActive) {
|
||||||
|
Terminal._clearStorage();
|
||||||
|
Terminal._hideTimer();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
|
||||||
enableChat(agentId, agentName, sessionId) {
|
enableChat(agentId, agentName, sessionId) {
|
||||||
Terminal._chatSession = { agentId, agentName, sessionId };
|
Terminal._chatSession = { agentId, agentName, sessionId };
|
||||||
|
try { sessionStorage.setItem(Terminal._chatStorageKey, JSON.stringify(Terminal._chatSession)); } catch {}
|
||||||
const bar = document.getElementById('terminal-input-bar');
|
const bar = document.getElementById('terminal-input-bar');
|
||||||
const ctx = document.getElementById('terminal-input-context');
|
const ctx = document.getElementById('terminal-input-context');
|
||||||
const input = document.getElementById('terminal-input');
|
const input = document.getElementById('terminal-input');
|
||||||
@@ -21,6 +72,7 @@ const Terminal = {
|
|||||||
|
|
||||||
disableChat() {
|
disableChat() {
|
||||||
Terminal._chatSession = null;
|
Terminal._chatSession = null;
|
||||||
|
try { sessionStorage.removeItem(Terminal._chatStorageKey); } catch {}
|
||||||
const bar = document.getElementById('terminal-input-bar');
|
const bar = document.getElementById('terminal-input-bar');
|
||||||
if (bar) bar.hidden = true;
|
if (bar) bar.hidden = true;
|
||||||
},
|
},
|
||||||
@@ -43,13 +95,55 @@ const Terminal = {
|
|||||||
Terminal.lines.shift();
|
Terminal.lines.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Terminal._saveToStorage();
|
||||||
Terminal.render();
|
Terminal.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_startTimer(fromTimestamp) {
|
||||||
|
Terminal._stopTimer();
|
||||||
|
Terminal._timerStart = fromTimestamp || Date.now();
|
||||||
|
try { sessionStorage.setItem(Terminal._timerStorageKey, String(Terminal._timerStart)); } catch {}
|
||||||
|
|
||||||
|
const timerEl = document.getElementById('terminal-timer');
|
||||||
|
const valueEl = document.getElementById('terminal-timer-value');
|
||||||
|
if (timerEl) timerEl.hidden = false;
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
if (!valueEl) return;
|
||||||
|
const elapsed = Math.floor((Date.now() - Terminal._timerStart) / 1000);
|
||||||
|
const h = Math.floor(elapsed / 3600);
|
||||||
|
const m = Math.floor((elapsed % 3600) / 60);
|
||||||
|
const s = elapsed % 60;
|
||||||
|
valueEl.textContent = h > 0
|
||||||
|
? `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
|
||||||
|
: `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
tick();
|
||||||
|
Terminal._timerInterval = setInterval(tick, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
_stopTimer() {
|
||||||
|
if (Terminal._timerInterval) {
|
||||||
|
clearInterval(Terminal._timerInterval);
|
||||||
|
Terminal._timerInterval = null;
|
||||||
|
}
|
||||||
|
try { sessionStorage.removeItem(Terminal._timerStorageKey); } catch {}
|
||||||
|
},
|
||||||
|
|
||||||
|
_hideTimer() {
|
||||||
|
Terminal._stopTimer();
|
||||||
|
const timerEl = document.getElementById('terminal-timer');
|
||||||
|
if (timerEl) timerEl.hidden = true;
|
||||||
|
},
|
||||||
|
|
||||||
startProcessing(agentName) {
|
startProcessing(agentName) {
|
||||||
Terminal.stopProcessing();
|
Terminal.stopProcessing();
|
||||||
Terminal.addLine(`Agente "${agentName}" processando tarefa...`, 'system');
|
Terminal.addLine(`Agente "${agentName}" processando tarefa...`, 'system');
|
||||||
|
|
||||||
|
if (!Terminal._timerInterval) {
|
||||||
|
Terminal._startTimer();
|
||||||
|
}
|
||||||
|
|
||||||
let dots = 0;
|
let dots = 0;
|
||||||
Terminal._processingInterval = setInterval(() => {
|
Terminal._processingInterval = setInterval(() => {
|
||||||
dots = (dots + 1) % 4;
|
dots = (dots + 1) % 4;
|
||||||
@@ -71,8 +165,10 @@ const Terminal = {
|
|||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
Terminal.stopProcessing();
|
Terminal.stopProcessing();
|
||||||
|
Terminal._hideTimer();
|
||||||
Terminal.lines = [];
|
Terminal.lines = [];
|
||||||
Terminal.executionFilter = null;
|
Terminal.executionFilter = null;
|
||||||
|
Terminal._clearStorage();
|
||||||
Terminal.render();
|
Terminal.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user