Files
PiDoBot/templates/index.html

157 lines
6.8 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Pi-Orchestrator AI</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/split.js/1.6.0/split.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.1.0/css/xterm.css" />
<script src="https://cdn.jsdelivr.net/npm/xterm@5.1.0/lib/xterm.js"></script>
<style>
/* Styles für die ziehbaren Trennbalken (Gutters) */
.gutter {
background-color: #2d3748;
background-repeat: no-repeat;
background-position: center;
}
.gutter.gutter-horizontal {
cursor: col-resize;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAG8aZGCAwMA6gBVHicPXM2nPRDAADy9GAVu4E9ngAAAABJRU5ErkJggg==');
}
.gutter.gutter-vertical {
cursor: row-resize;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABjU9S9AAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
}
.split { display: flex; flex-direction: row; }
#upper-area { display: flex; flex-direction: row; width: 100%; }
#lower-area { display: flex; flex-direction: row; width: 100%; }
</style>
</head>
<body class="bg-gray-900 text-white h-screen flex flex-col overflow-hidden">
<div id="vertical-split" class="flex flex-col flex-1">
<div id="upper-area" class="flex">
<div id="sidebar" class="bg-gray-800 p-4 overflow-y-auto">
<h2 class="text-xl font-bold mb-4">📍 Nodes</h2>
<div id="node-list" class="space-y-2">
{% for node in nodes %}
<div class="p-3 bg-gray-700 rounded border border-gray-600 relative group">
<form action="/remove_node/{{ node.id }}" method="post" class="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button type="submit" class="text-red-500 font-bold px-1">×</button>
</form>
<div class="text-sm font-bold">{{ node.name }}</div>
<div class="text-[10px] text-gray-400">{{ node.ip }}</div>
<button onclick="openTerminal('{{ node.ip }}')" class="mt-2 text-[10px] bg-blue-600 hover:bg-blue-500 px-2 py-1 rounded w-full">Terminal</button>
</div>
{% endfor %}
</div>
<button onclick="addNode()" class="mt-4 w-full bg-green-700 p-2 rounded text-sm">+ Add</button>
</div>
<div id="chat-area" class="flex flex-col bg-gray-900">
<div id="chat-window" class="flex-1 p-4 overflow-y-auto space-y-2">
<div class="text-gray-500 italic text-sm">System bereit...</div>
</div>
<div class="p-3 border-t border-gray-700 flex bg-gray-800">
<input id="user-input" type="text" class="flex-1 bg-gray-700 p-2 rounded-l outline-none text-sm" placeholder="KI Befehl...">
<button onclick="sendMessage()" class="bg-blue-600 px-4 rounded-r">Send</button>
</div>
</div>
</div>
<div id="lower-area" class="flex bg-black">
<div id="log-area" class="p-2 font-mono text-xs text-green-400 overflow-y-auto border-r border-gray-800">
<div class="text-gray-600 border-b border-gray-800 mb-1 uppercase tracking-tighter">System Logs</div>
<div id="install-log"></div>
</div>
<div id="terminal-area" class="p-1">
<div id="terminal" class="h-full w-full"></div>
</div>
</div>
</div>
<script>
// --- Split.js Initialisierung ---
// 1. Vertikaler Split (Oben vs Unten)
Split(['#upper-area', '#lower-area'], {
direction: 'vertical',
sizes: [65, 35], // 65% Oben, 35% Unten
minSize: 100,
gutterSize: 8,
});
// 2. Horizontaler Split Oben (Sidebar vs Chat)
Split(['#sidebar', '#chat-area'], {
sizes: [20, 80],
minSize: [150, 300],
gutterSize: 8,
});
// 3. Horizontaler Split Unten (Logs vs Terminal)
Split(['#log-area', '#terminal-area'], {
sizes: [30, 70],
minSize: [100, 300],
gutterSize: 8,
onDrag: () => {
// Terminal an neue Größe anpassen (falls Fit-Addon genutzt wird)
}
});
// --- Terminal & WebSockets ---
const term = new Terminal({ theme: { background: '#000' }, cursorBlink: true, fontSize: 13 });
term.open(document.getElementById('terminal'));
let termWs = null;
function openTerminal(ip) {
if(termWs) termWs.close();
term.clear();
term.write(`\r\n>>> Verbinde mit ${ip}...\r\n`);
termWs = new WebSocket(`ws://${location.host}/ws/terminal/${ip}`);
termWs.onmessage = (ev) => term.write(ev.data);
term.onData(data => termWs.send(data));
}
const logWs = new WebSocket(`ws://${location.host}/ws/install_logs`);
logWs.onmessage = (ev) => {
const l = document.getElementById('install-log');
l.innerHTML += `<div>${ev.data}</div>`;
l.parentElement.scrollTop = l.parentElement.scrollHeight;
};
const chatWs = new WebSocket(`ws://${location.host}/ws/chat`);
chatWs.onmessage = (ev) => appendChat("Bot", ev.data, "text-blue-400");
function sendMessage() {
const i = document.getElementById('user-input');
chatWs.send(i.value);
appendChat("Du", i.value, "text-white");
i.value = '';
}
function appendChat(user, msg, color) {
const div = document.createElement('div');
div.className = "text-sm border-l-2 border-gray-700 pl-2 py-1";
div.innerHTML = `<span class="${color} font-bold">${user}:</span> ${msg}`;
document.getElementById('chat-window').appendChild(div);
}
async function addNode() {
const name = prompt("Name:");
const ip = prompt("IP:");
const user = prompt("User:", "pi");
const pass = prompt("Passwort:");
if(name && ip && pass) {
const fd = new FormData();
fd.append('name', name); fd.append('ip', ip);
fd.append('user', user); fd.append('password', pass);
await fetch('/add_node', { method: 'POST', body: fd });
location.reload();
}
}
</script>
</body>
</html>