fix windows and write credentials

This commit is contained in:
duffyduck 2026-03-12 18:57:18 +01:00
parent 3a82f9bab0
commit 9783de85f5
3 changed files with 89 additions and 31 deletions

View File

@ -12,10 +12,12 @@ Alle Änderungen am Projekt. Format: [Keep a Changelog](https://keepachangelog.c
- Neuer Container `aria-diagnostic` mit Web-UI auf Port 3001 - Neuer Container `aria-diagnostic` mit Web-UI auf Port 3001
- Status-Karten: OpenClaw Gateway, RVS, Claude Proxy — jeweils mit Dot-Indicator - Status-Karten: OpenClaw Gateway, RVS, Claude Proxy — jeweils mit Dot-Indicator
- Claude Proxy Test: Prüft Erreichbarkeit (`/v1/models`) und sendet Test-Prompt an Claude — zeigt verfügbare Modelle als Tags + `DEFAULT_MODEL` Hinweis für docker-compose.yml - Claude Proxy Test: Prüft Erreichbarkeit (`/v1/models`) und sendet Test-Prompt an Claude — zeigt verfügbare Modelle als Tags + `DEFAULT_MODEL` Hinweis für docker-compose.yml
- Auth-Check: "Auth prüfen" Button zeigt Inhalt von `/root/.config/claude/` im Proxy-Container (Dateien + `.credentials.json`) — per Docker Exec API - Auth-Check: "Auth prüfen" Button durchsucht alle bekannten Credential-Pfade im Proxy-Container (`/root/.config/claude/`, `/root/.claude/`, `/root/.claude/auth/`) rekursiv — zeigt gefundene Dateien und deren Inhalt
- Claude Login via UI: "Login starten" Button führt `claude login` (mit `TERM=dumb NO_COLOR=1 CI=1`) im Proxy-Container aus, streamt Output live, ANSI-Codes werden gestrippt - Claude Login via UI: "Login starten" Button öffnet interaktives Terminal (xterm.js) in einem Modal-Overlay — führt `claude login` im Proxy-Container aus, volle TUI-Unterstützung (kein ANSI-Stripping mehr nötig)
- Credentials manuell einfügen: "Credentials einfügen" Button — JSON von einem eingeloggten Rechner (`cat ~/.config/claude/.credentials.json`) kopieren und direkt in den Container schreiben - xterm.js Terminal: Bidirektionaler Stream über Docker Exec API mit `Tty: true` + HTTP Upgrade auf Raw-TCP-Socket — echtes interaktives Terminal im Browser
- Docker Exec API: Generische `dockerExec()` + Streaming-Variante `dockerExecStreaming()` für Befehle in laufenden Containern (via Docker Socket) - UTF-8 Fix: Eingehende Daten werden als `Uint8Array` an xterm.write() übergeben (statt `atob()` → Latin-1 String, der Multi-Byte UTF-8 zerstört), ausgehende Daten über `TextEncoder` UTF-8-safe kodiert
- Credentials manuell einfügen: "Credentials einfügen" Button — JSON von einem eingeloggten Rechner kopieren und direkt in den Container schreiben (schreibt in beide mögliche Pfade: `.config/claude/` und `.claude/`)
- Docker Exec API: Generische `dockerExec()` (nicht-interaktiv, multiplexed stream) + `attachTerminal()` (interaktiv, Tty, raw TCP socket) für Befehle in laufenden Containern (via Docker Socket)
- Chat-Test: Nachrichten direkt über Gateway oder via RVS senden - Chat-Test: Nachrichten direkt über Gateway oder via RVS senden
- Tabbed Logs: Separate Tabs für Alle, Gateway, RVS, Proxy, Server — mit Zähler pro Tab - Tabbed Logs: Separate Tabs für Alle, Gateway, RVS, Proxy, Server — mit Zähler pro Tab
- Autoscroll-Pause: Automatisch wenn hochgescrollt, "Nach unten" Button zum Fortsetzen - Autoscroll-Pause: Automatisch wenn hochgescrollt, "Nach unten" Button zum Fortsetzen

View File

@ -69,6 +69,20 @@
.input-row { display: flex; gap: 6px; } .input-row { display: flex; gap: 6px; }
.input-row input { flex: 1; background: #1E1E2E; border: 1px solid #333; border-radius: 6px; .input-row input { flex: 1; background: #1E1E2E; border: 1px solid #333; border-radius: 6px;
padding: 8px 12px; color: #E0E0F0; font-family: inherit; font-size: 13px; } padding: 8px 12px; color: #E0E0F0; font-family: inherit; font-size: 13px; }
/* Terminal Modal */
.modal-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.85);
z-index:1000; justify-content:center; align-items:center; }
.modal-overlay.open { display:flex; }
.modal-box { background:#0D0D1A; border:1px solid #FFD60A44; border-radius:10px; width:90vw; max-width:860px;
max-height:90vh; display:flex; flex-direction:column; overflow:hidden; }
.modal-header { display:flex; justify-content:space-between; align-items:center; padding:10px 14px;
border-bottom:1px solid #1E1E2E; }
.modal-header h3 { font-size:14px; color:#FFD60A; margin:0; }
.modal-close { background:none; border:none; color:#888; font-size:20px; cursor:pointer; padding:0 6px; }
.modal-close:hover { color:#FF3B30; }
.modal-body { flex:1; padding:4px; min-height:400px; }
.modal-footer { padding:6px 14px; border-top:1px solid #1E1E2E; font-size:11px; color:#8888AA; }
</style> </style>
</head> </head>
<body> <body>
@ -108,14 +122,8 @@
<div id="proxy-models-list" style="font-size:12px;line-height:1.6"></div> <div id="proxy-models-list" style="font-size:12px;line-height:1.6"></div>
<div style="font-size:10px;color:#555570;margin-top:4px" id="proxy-models-hint"></div> <div style="font-size:10px;color:#555570;margin-top:4px" id="proxy-models-hint"></div>
</div> </div>
<div id="proxy-auth" style="margin-top:6px;display:none;background:#080810;border:1px solid #1E1E2E;border-radius:4px;padding:6px 8px;font-size:10px;line-height:1.5;max-height:120px;overflow-y:auto;white-space:pre-wrap;color:#8888AA"></div> <div id="proxy-auth" style="margin-top:6px;display:none;background:#080810;border:1px solid #1E1E2E;border-radius:4px;padding:6px 8px;font-size:10px;line-height:1.5;max-height:220px;overflow-y:auto;white-space:pre-wrap;color:#8888AA"></div>
<div id="proxy-term-box" style="margin-top:6px;display:none"> <!-- Terminal Modal wird weiter unten definiert -->
<div style="background:#000;border:1px solid #FFD60A33;border-radius:6px;padding:4px;position:relative">
<div style="font-size:11px;color:#FFD60A;padding:4px 6px;font-weight:bold">Claude Login Terminal</div>
<div id="terminal-container" style="height:320px"></div>
<div id="term-status" style="font-size:10px;color:#8888AA;padding:4px 6px"></div>
</div>
</div>
<div id="proxy-creds-box" style="margin-top:6px;display:none"> <div id="proxy-creds-box" style="margin-top:6px;display:none">
<div style="background:#0A1A1A;border:1px solid #0096FF33;border-radius:6px;padding:8px 10px"> <div style="background:#0A1A1A;border:1px solid #0096FF33;border-radius:6px;padding:8px 10px">
<div style="font-size:11px;color:#0096FF;margin-bottom:4px;font-weight:bold">Credentials einfuegen</div> <div style="font-size:11px;color:#0096FF;margin-bottom:4px;font-weight:bold">Credentials einfuegen</div>
@ -176,6 +184,18 @@
</div> </div>
</div> </div>
<!-- Terminal Modal -->
<div class="modal-overlay" id="term-modal">
<div class="modal-box">
<div class="modal-header">
<h3>Claude Login Terminal</h3>
<button class="modal-close" onclick="closeTermModal()">&times;</button>
</div>
<div class="modal-body" id="terminal-container"></div>
<div class="modal-footer" id="term-status"></div>
</div>
</div>
<script> <script>
const chatBox = document.getElementById('chat-box'); const chatBox = document.getElementById('chat-box');
const pauseHint = document.getElementById('pause-hint'); const pauseHint = document.getElementById('pause-hint');
@ -317,16 +337,17 @@
} }
if (msg.type === 'term_data') { if (msg.type === 'term_data') {
if (term) { if (term) {
const bytes = atob(msg.data); // base64 → Uint8Array (UTF-8 safe)
const raw = atob(msg.data);
const bytes = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i++) bytes[i] = raw.charCodeAt(i);
term.write(bytes); term.write(bytes);
} }
return; return;
} }
if (msg.type === 'term_exit') { if (msg.type === 'term_exit') {
if (term) term.writeln('\r\n\x1b[33m--- Session beendet ---\x1b[0m'); if (term) term.writeln('\r\n\x1b[33m--- Session beendet ---\x1b[0m');
document.getElementById('term-status').textContent = 'Session beendet'; document.getElementById('term-status').textContent = 'Session beendet — Fenster schliessen oder erneut einloggen';
document.getElementById('btn-proxy-login').disabled = false;
document.getElementById('btn-proxy-login').textContent = 'Erneut einloggen';
return; return;
} }
if (msg.type === 'term_error') { if (msg.type === 'term_error') {
@ -381,13 +402,13 @@
} }
let term = null; let term = null;
let termFitAddon = null;
function startProxyLogin() { function startProxyLogin() {
document.getElementById('proxy-term-box').style.display = 'block'; document.getElementById('term-modal').classList.add('open');
document.getElementById('btn-proxy-login').disabled = true; document.getElementById('btn-proxy-login').disabled = true;
document.getElementById('term-status').textContent = 'Starte Terminal...'; document.getElementById('term-status').textContent = 'Starte Terminal...';
// xterm.js laden falls noch nicht geladen
if (typeof Terminal === 'undefined') { if (typeof Terminal === 'undefined') {
const s = document.createElement('script'); const s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js'; s.src = 'https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js';
@ -403,38 +424,50 @@
} }
} }
function closeTermModal() {
document.getElementById('term-modal').classList.remove('open');
document.getElementById('btn-proxy-login').disabled = false;
document.getElementById('btn-proxy-login').textContent = 'Login starten';
// Terminal aufräumen
if (term) { term.dispose(); term = null; }
}
function initTerminal() { function initTerminal() {
const container = document.getElementById('terminal-container'); const container = document.getElementById('terminal-container');
container.innerHTML = ''; container.innerHTML = '';
term = new Terminal({ term = new Terminal({
cursorBlink: true, cursorBlink: true,
fontSize: 13, fontSize: 14,
fontFamily: "'Courier New', monospace", fontFamily: "'DejaVu Sans Mono', 'Courier New', monospace",
theme: { background: '#000000', foreground: '#E0E0F0' }, theme: { background: '#000000', foreground: '#E0E0F0' },
cols: 80,
rows: 20,
}); });
if (window.FitAddon) { if (window.FitAddon) {
const fitAddon = new FitAddon.FitAddon(); termFitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon); term.loadAddon(termFitAddon);
term.open(container); term.open(container);
fitAddon.fit(); termFitAddon.fit();
} else { } else {
term.open(container); term.open(container);
} }
// Tastatureingabe → Server // Tastatureingabe → Server (UTF-8 safe)
term.onData((data) => { term.onData((data) => {
const b64 = btoa(unescape(encodeURIComponent(data))); const encoder = new TextEncoder();
send({ action: 'term_input', data: b64 }); const bytes = encoder.encode(data);
let b64 = '';
for (let i = 0; i < bytes.length; i++) b64 += String.fromCharCode(bytes[i]);
send({ action: 'term_input', data: btoa(b64) });
}); });
term.writeln('\x1b[33mVerbinde mit Proxy-Container...\x1b[0m'); term.writeln('\x1b[33mVerbinde mit Proxy-Container...\x1b[0m');
send({ action: 'proxy_login' }); send({ action: 'proxy_login' });
} }
// Resize bei Fensteraenderung
window.addEventListener('resize', () => { if (termFitAddon && term) termFitAddon.fit(); });
function toggleCredsBox() { function toggleCredsBox() {
const box = document.getElementById('proxy-creds-box'); const box = document.getElementById('proxy-creds-box');
box.style.display = box.style.display === 'none' ? 'block' : 'none'; box.style.display = box.style.display === 'none' ? 'block' : 'none';

View File

@ -376,7 +376,7 @@ async function testProxy(prompt) {
// Schritt 1b: Auth-Dateien im Proxy-Container pruefen // Schritt 1b: Auth-Dateien im Proxy-Container pruefen
try { try {
const authInfo = await dockerExec("aria-proxy", "ls -la /root/.config/claude/ 2>&1 && echo '---' && cat /root/.config/claude/.credentials.json 2>/dev/null | head -c 500 || echo '(keine .credentials.json)'"); const authInfo = await dockerExec("aria-proxy", "echo '--- /root/.config/claude/ ---' && ls -la /root/.config/claude/ 2>&1 && echo '--- /root/.claude/ ---' && ls -la /root/.claude/ 2>&1 && echo '--- Credential-Dateien ---' && find /root/.config/claude /root/.claude -name '*.json' -o -name '*credential*' -o -name '*auth*' -o -name '*token*' 2>/dev/null | head -20");
log("info", "proxy", `Auth-Dateien im Container:\n${authInfo}`); log("info", "proxy", `Auth-Dateien im Container:\n${authInfo}`);
broadcast({ type: "proxy_auth", info: authInfo }); broadcast({ type: "proxy_auth", info: authInfo });
} catch (authErr) { } catch (authErr) {
@ -550,7 +550,8 @@ async function writeProxyCredentials(credentialsJson) {
// Escaped fuer Shell — Einfache Anfuehrungszeichen im JSON escapen // Escaped fuer Shell — Einfache Anfuehrungszeichen im JSON escapen
const escaped = credentialsJson.replace(/'/g, "'\\''"); const escaped = credentialsJson.replace(/'/g, "'\\''");
await dockerExec("aria-proxy", `mkdir -p /root/.config/claude && echo '${escaped}' > /root/.config/claude/.credentials.json`); // In beide moegliche Speicherorte schreiben
await dockerExec("aria-proxy", `mkdir -p /root/.config/claude && echo '${escaped}' > /root/.config/claude/.credentials.json && mkdir -p /root/.claude && echo '${escaped}' > /root/.claude/credentials.json`);
log("info", "proxy", "Credentials geschrieben!"); log("info", "proxy", "Credentials geschrieben!");
broadcast({ type: "login_status", status: "done" }); broadcast({ type: "login_status", status: "done" });
@ -567,7 +568,29 @@ async function writeProxyCredentials(credentialsJson) {
async function checkProxyAuth() { async function checkProxyAuth() {
try { try {
log("info", "proxy", "Pruefe Auth-Dateien im Proxy-Container..."); log("info", "proxy", "Pruefe Auth-Dateien im Proxy-Container...");
const authInfo = await dockerExec("aria-proxy", "echo '=== /root/.config/claude/ ===' && ls -la /root/.config/claude/ 2>&1 && echo '' && echo '=== .credentials.json ===' && cat /root/.config/claude/.credentials.json 2>/dev/null || echo '(nicht vorhanden)'"); // Breit suchen: Claude Code speichert Credentials je nach Version an verschiedenen Orten
const authInfo = await dockerExec("aria-proxy", `
echo '=== /root/.config/claude/ ===' &&
ls -la /root/.config/claude/ 2>&1 &&
echo '' &&
echo '=== /root/.claude/ ===' &&
ls -la /root/.claude/ 2>&1 &&
echo '' &&
echo '=== /root/.claude/auth/ ===' &&
ls -la /root/.claude/auth/ 2>&1 &&
echo '' &&
echo '=== Credentials-Dateien (rekursiv) ===' &&
find /root/.config/claude /root/.claude -name '*.json' -o -name '*credential*' -o -name '*auth*' -o -name '*token*' -o -name '*oauth*' -o -name '*session*' 2>/dev/null | head -20 &&
echo '' &&
echo '=== .credentials.json ===' &&
cat /root/.config/claude/.credentials.json 2>/dev/null || echo '(nicht in .config/claude/)' &&
echo '' &&
echo '=== /root/.claude/credentials.json ===' &&
cat /root/.claude/credentials.json 2>/dev/null || echo '(nicht in .claude/)' &&
echo '' &&
echo '=== /root/.claude/auth/*.json ===' &&
cat /root/.claude/auth/*.json 2>/dev/null || echo '(keine auth/*.json)'
`.trim());
log("info", "proxy", `Auth-Dateien:\n${authInfo}`); log("info", "proxy", `Auth-Dateien:\n${authInfo}`);
broadcast({ type: "proxy_auth", info: authInfo }); broadcast({ type: "proxy_auth", info: authInfo });
} catch (err) { } catch (err) {