179 lines
5.5 KiB
HTML
179 lines
5.5 KiB
HTML
{{define "console"}}
|
|
<!DOCTYPE html>
|
|
<html lang="sr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>KAOS — Konzola</title>
|
|
<link rel="stylesheet" href="/static/style.css">
|
|
<script src="/static/htmx.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>🔧 KAOS Dashboard</h1>
|
|
<div class="header-right">
|
|
<nav class="nav">
|
|
<a href="/" class="btn">Kanban</a>
|
|
<a href="/docs" class="btn">Dokumenti</a>
|
|
<a href="/console" class="btn btn-active">Konzola</a>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="console-container">
|
|
<div class="console-toolbar">
|
|
<button class="btn" id="toggle-panel" onclick="togglePanel2()">+ Sesija 2</button>
|
|
</div>
|
|
|
|
<div class="console-panels">
|
|
<div class="console-panel" id="panel-1">
|
|
<div class="console-panel-header">
|
|
<span>🔧 Sesija 1</span>
|
|
<span class="session-status" id="status-1">idle</span>
|
|
<button class="btn btn-kill" id="kill-1" onclick="killSession(1)" style="display:none">Prekini</button>
|
|
</div>
|
|
<div class="console-output" id="output-1"></div>
|
|
<div class="console-input-row">
|
|
<input type="text" id="input-1" class="console-input" placeholder="Komanda..." onkeydown="handleKey(event, 1)" autocomplete="off">
|
|
<button class="btn btn-move" onclick="sendCommand(1)">⏎</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="console-panel" id="panel-2" style="display:none">
|
|
<div class="console-panel-header">
|
|
<span>🔧 Sesija 2</span>
|
|
<span class="session-status" id="status-2">idle</span>
|
|
<button class="btn btn-kill" id="kill-2" onclick="killSession(2)" style="display:none">Prekini</button>
|
|
</div>
|
|
<div class="console-output" id="output-2"></div>
|
|
<div class="console-input-row">
|
|
<input type="text" id="input-2" class="console-input" placeholder="Komanda..." onkeydown="handleKey(event, 2)" autocomplete="off">
|
|
<button class="btn btn-move" onclick="sendCommand(2)">⏎</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var historyIdx = [0, 0];
|
|
var cmdHistory = [[], []];
|
|
|
|
function handleKey(e, session) {
|
|
if (e.key === 'Enter') {
|
|
sendCommand(session);
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
var idx = session - 1;
|
|
if (historyIdx[idx] > 0) {
|
|
historyIdx[idx]--;
|
|
document.getElementById('input-' + session).value = cmdHistory[idx][historyIdx[idx]] || '';
|
|
}
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
var idx = session - 1;
|
|
if (historyIdx[idx] < cmdHistory[idx].length) {
|
|
historyIdx[idx]++;
|
|
document.getElementById('input-' + session).value = cmdHistory[idx][historyIdx[idx]] || '';
|
|
}
|
|
}
|
|
}
|
|
|
|
function sendCommand(session) {
|
|
var input = document.getElementById('input-' + session);
|
|
var cmd = input.value.trim();
|
|
if (!cmd) return;
|
|
|
|
var idx = session - 1;
|
|
cmdHistory[idx].push(cmd);
|
|
historyIdx[idx] = cmdHistory[idx].length;
|
|
|
|
var output = document.getElementById('output-' + session);
|
|
output.innerHTML += '<div class="console-cmd">> ' + escapeHtml(cmd) + '</div>';
|
|
input.value = '';
|
|
|
|
setSessionUI(session, 'running');
|
|
|
|
fetch('/console/exec', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({cmd: cmd, session: session})
|
|
})
|
|
.then(function(resp) {
|
|
if (!resp.ok) {
|
|
return resp.json().then(function(data) {
|
|
output.innerHTML += '<div class="console-error">' + escapeHtml(data.error) + '</div>';
|
|
setSessionUI(session, 'idle');
|
|
throw new Error(data.error);
|
|
});
|
|
}
|
|
return resp.json();
|
|
})
|
|
.then(function(data) {
|
|
if (!data) return;
|
|
streamOutput(session, data.exec_id);
|
|
})
|
|
.catch(function(err) {
|
|
setSessionUI(session, 'idle');
|
|
});
|
|
}
|
|
|
|
function streamOutput(session, execId) {
|
|
var output = document.getElementById('output-' + session);
|
|
var source = new EventSource('/console/stream/' + execId);
|
|
|
|
source.onmessage = function(e) {
|
|
output.innerHTML += '<div class="console-line">' + escapeHtml(e.data) + '</div>';
|
|
output.scrollTop = output.scrollHeight;
|
|
};
|
|
|
|
source.addEventListener('done', function(e) {
|
|
source.close();
|
|
output.innerHTML += '<div class="console-done">--- gotovo ---</div>';
|
|
output.scrollTop = output.scrollHeight;
|
|
setSessionUI(session, 'idle');
|
|
});
|
|
|
|
source.onerror = function() {
|
|
source.close();
|
|
setSessionUI(session, 'idle');
|
|
};
|
|
}
|
|
|
|
function killSession(session) {
|
|
fetch('/console/kill/' + session, {method: 'POST'})
|
|
.then(function() {
|
|
var output = document.getElementById('output-' + session);
|
|
output.innerHTML += '<div class="console-error">--- prekinuto ---</div>';
|
|
setSessionUI(session, 'idle');
|
|
});
|
|
}
|
|
|
|
function setSessionUI(session, status) {
|
|
document.getElementById('status-' + session).textContent = status;
|
|
document.getElementById('status-' + session).className = 'session-status session-' + status;
|
|
document.getElementById('kill-' + session).style.display = status === 'running' ? 'inline-block' : 'none';
|
|
document.getElementById('input-' + session).disabled = status === 'running';
|
|
}
|
|
|
|
function togglePanel2() {
|
|
var panel = document.getElementById('panel-2');
|
|
var btn = document.getElementById('toggle-panel');
|
|
if (panel.style.display === 'none') {
|
|
panel.style.display = 'flex';
|
|
btn.textContent = '- Sesija 2';
|
|
} else {
|
|
panel.style.display = 'none';
|
|
btn.textContent = '+ Sesija 2';
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
{{end}}
|