KAOS/code/web/templates/console.html
djuka 932ffe5203 Konzola: interaktivni claude CLI + panic fix
- Svaka konzola sesija pokreće interaktivni claude (ne bash)
- Fix panic: send on closed channel kad se WS diskonektuje
- Tema: Claude Code boje (#0d1117 pozadina)
- PTY readLoop logging za debug

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 04:04:39 +00:00

238 lines
7.9 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>
<script>(function(){var m=localStorage.getItem('kaos-theme')||'dark',t=m;if(m==='auto'){t=window.matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'}document.documentElement.setAttribute('data-theme',t)})()</script>
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css">
<script src="/static/htmx.min.js"></script>
<script src="/static/theme.js"></script>
</head>
<body>
<div class="header">
<h1>🔧 KAOS Dashboard</h1>
<div class="header-right">
<div class="theme-toggle">
<button class="theme-btn" data-theme-mode="light" onclick="setTheme('light')" title="Svetla tema">☀️</button>
<button class="theme-btn" data-theme-mode="dark" onclick="setTheme('dark')" title="Tamna tema">🌙</button>
<button class="theme-btn" data-theme-mode="auto" onclick="setTheme('auto')" title="Sistemska tema">🔄</button>
</div>
<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>
<a href="/submit" class="btn">Prijava</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 session-running" id="status-1">connected</span>
</div>
<div class="console-terminal" id="terminal-1"></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>
</div>
<div class="console-terminal" id="terminal-2"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.min.js"></script>
<script>
// ── Terminal themes ──────────────────────────────────
var TERM_THEMES = {
dark: {
background: '#0d1117', foreground: '#e0e0e0', cursor: '#e94560', cursorAccent: '#0d1117',
selectionBackground: 'rgba(233,69,96,0.3)',
black: '#0d1117', red: '#f44336', green: '#4caf50', yellow: '#ff9800',
blue: '#2196f3', magenta: '#e94560', cyan: '#00bcd4', white: '#e0e0e0',
brightBlack: '#6c6c80', brightRed: '#ff6b81', brightGreen: '#66bb6a', brightYellow: '#ffb74d',
brightBlue: '#64b5f6', brightMagenta: '#ff6b81', brightCyan: '#4dd0e1', brightWhite: '#ffffff'
},
light: {
background: '#f5f6fa', foreground: '#1e293b', cursor: '#d63851', cursorAccent: '#f5f6fa',
selectionBackground: 'rgba(214,56,81,0.15)',
black: '#1e293b', red: '#dc322f', green: '#859900', yellow: '#b58900',
blue: '#268bd2', magenta: '#d63851', cyan: '#2aa198', white: '#eee8d5',
brightBlack: '#586e75', brightRed: '#cb4b16', brightGreen: '#586e75', brightYellow: '#657b83',
brightBlue: '#839496', brightMagenta: '#6c71c4', brightCyan: '#93a1a1', brightWhite: '#002b36'
}
};
function getTermTheme() {
var t = document.documentElement.getAttribute('data-theme') || 'dark';
return TERM_THEMES[t] || TERM_THEMES.dark;
}
// ── Session state ────────────────────────────────────
var sessions = [{}, {}];
function initTerminal(idx) {
var num = idx + 1;
var containerEl = document.getElementById('terminal-' + num);
var theme = getTermTheme();
var term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
cursorInactiveStyle: 'outline',
fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace",
theme: theme,
allowProposedApi: true,
scrollback: 10000,
convertEol: false,
drawBoldTextInBrightColors: true
});
var fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.loadAddon(new WebLinksAddon.WebLinksAddon());
term.open(containerEl);
// Keyboard input → WebSocket
term.onData(function(data) {
var ws = sessions[idx].ws;
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(data);
}
});
// Resize → WebSocket
term.onResize(function(size) {
var ws = sessions[idx].ws;
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'resize', cols: size.cols, rows: size.rows }));
}
});
containerEl.addEventListener('click', function() { term.focus(); });
sessions[idx].term = term;
sessions[idx].fitAddon = fitAddon;
sessions[idx].ws = null;
setTimeout(function() {
fitAddon.fit();
// Auto-connect WebSocket immediately
connectWS(idx);
}, 100);
}
// ── WebSocket connection ─────────────────────────────
function connectWS(idx) {
var num = idx + 1;
var sess = sessions[idx];
if (sess.ws) {
sess.ws.close();
sess.ws = null;
}
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
var url = proto + '//' + location.host + '/console/ws/' + num;
var ws = new WebSocket(url);
ws.binaryType = 'arraybuffer';
ws.onopen = function() {
setSessionUI(num, 'connected');
var term = sess.term;
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
term.focus();
};
ws.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
sess.term.write(new Uint8Array(event.data));
} else {
sess.term.write(event.data);
}
};
ws.onclose = function() {
sess.ws = null;
setSessionUI(num, 'disconnected');
};
ws.onerror = function() {
sess.ws = null;
setSessionUI(num, 'error');
};
sess.ws = ws;
}
// ── UI helpers ───────────────────────────────────────
function setSessionUI(session, status) {
var el = document.getElementById('status-' + session);
el.textContent = status;
el.className = 'session-status';
if (status === 'connected') el.className += ' session-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';
if (!sessions[1].term) {
initTerminal(1);
} else {
sessions[1].fitAddon.fit();
if (!sessions[1].ws) connectWS(1);
}
} else {
panel.style.display = 'none';
btn.textContent = '+ Sesija 2';
}
}
// ── Theme sync ───────────────────────────────────────
var origSetTheme = window.setTheme;
window.setTheme = function(mode) {
if (origSetTheme) origSetTheme(mode);
setTimeout(function() {
var theme = getTermTheme();
for (var i = 0; i < 2; i++) {
if (sessions[i].term) {
sessions[i].term.options.theme = theme;
}
}
}, 50);
};
// ── Window resize ────────────────────────────────────
window.addEventListener('resize', function() {
for (var i = 0; i < 2; i++) {
if (sessions[i].fitAddon) {
sessions[i].fitAddon.fit();
}
}
});
// ── Initialize ───────────────────────────────────────
initTerminal(0);
</script>
</body>
</html>
{{end}}