added xterm for login
This commit is contained in:
parent
0beef70651
commit
3a82f9bab0
|
|
@ -4,6 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ARIA Diagnostic</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #0D0D1A; color: #E0E0F0; font-family: 'Courier New', monospace; padding: 16px; }
|
||||
|
|
@ -108,15 +109,11 @@
|
|||
<div style="font-size:10px;color:#555570;margin-top:4px" id="proxy-models-hint"></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-login-box" style="margin-top:6px;display:none">
|
||||
<div style="background:#1A1A0A;border:1px solid #FFD60A33;border-radius:6px;padding:8px 10px">
|
||||
<div style="font-size:11px;color:#FFD60A;margin-bottom:6px;font-weight:bold">Claude Login</div>
|
||||
<div id="login-output" style="font-size:11px;line-height:1.6;color:#AAB;max-height:100px;overflow-y:auto;white-space:pre-wrap"></div>
|
||||
<div id="login-url-box" style="display:none;margin-top:6px">
|
||||
<div style="font-size:11px;color:#8888AA;margin-bottom:4px">Link oeffnen und autorisieren:</div>
|
||||
<a id="login-url-link" href="#" target="_blank" style="color:#0096FF;font-size:12px;word-break:break-all"></a>
|
||||
</div>
|
||||
<div id="login-done" style="display:none;margin-top:6px;color:#34C759;font-size:12px">Login abgeschlossen!</div>
|
||||
<div id="proxy-term-box" style="margin-top:6px;display:none">
|
||||
<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">
|
||||
|
|
@ -313,35 +310,37 @@
|
|||
el.textContent = msg.error ? `Fehler: ${msg.error}` : msg.info;
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'login_output') {
|
||||
const el = document.getElementById('login-output');
|
||||
el.textContent += msg.text + '\n';
|
||||
el.scrollTop = el.scrollHeight;
|
||||
if (msg.type === 'term_ready') {
|
||||
document.getElementById('term-status').textContent = 'Verbunden — interaktives Terminal';
|
||||
if (term) term.writeln('\x1b[32mVerbunden!\x1b[0m\r\n');
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'login_url') {
|
||||
const el = document.getElementById('login-output');
|
||||
el.textContent += msg.raw + '\n';
|
||||
el.scrollTop = el.scrollHeight;
|
||||
document.getElementById('login-url-box').style.display = 'block';
|
||||
const link = document.getElementById('login-url-link');
|
||||
link.href = msg.url;
|
||||
link.textContent = msg.url;
|
||||
if (msg.type === 'term_data') {
|
||||
if (term) {
|
||||
const bytes = atob(msg.data);
|
||||
term.write(bytes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'term_exit') {
|
||||
if (term) term.writeln('\r\n\x1b[33m--- Session beendet ---\x1b[0m');
|
||||
document.getElementById('term-status').textContent = 'Session beendet';
|
||||
document.getElementById('btn-proxy-login').disabled = false;
|
||||
document.getElementById('btn-proxy-login').textContent = 'Erneut einloggen';
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'term_error') {
|
||||
if (term) term.writeln('\r\n\x1b[31mFehler: ' + msg.error + '\x1b[0m');
|
||||
document.getElementById('btn-proxy-login').disabled = false;
|
||||
return;
|
||||
}
|
||||
if (msg.type === 'login_status') {
|
||||
if (msg.status === 'done') {
|
||||
document.getElementById('login-done').style.display = 'block';
|
||||
document.getElementById('btn-proxy-login').disabled = false;
|
||||
document.getElementById('btn-proxy-login').textContent = 'Erneut einloggen';
|
||||
const cs = document.getElementById('creds-status');
|
||||
cs.textContent = 'Erfolgreich!'; cs.style.color = '#34C759';
|
||||
if (cs) { cs.textContent = 'Erfolgreich!'; cs.style.color = '#34C759'; }
|
||||
} else if (msg.status === 'error') {
|
||||
const el = document.getElementById('login-output');
|
||||
el.textContent += 'FEHLER: ' + msg.error + '\n';
|
||||
document.getElementById('btn-proxy-login').disabled = false;
|
||||
const cs = document.getElementById('creds-status');
|
||||
cs.textContent = msg.error; cs.style.color = '#FF6B6B';
|
||||
if (cs) { cs.textContent = msg.error; cs.style.color = '#FF6B6B'; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -381,12 +380,58 @@
|
|||
send({ action: 'check_proxy_auth' });
|
||||
}
|
||||
|
||||
let term = null;
|
||||
|
||||
function startProxyLogin() {
|
||||
document.getElementById('proxy-login-box').style.display = 'block';
|
||||
document.getElementById('login-output').textContent = 'Starte Login...\n';
|
||||
document.getElementById('login-url-box').style.display = 'none';
|
||||
document.getElementById('login-done').style.display = 'none';
|
||||
document.getElementById('proxy-term-box').style.display = 'block';
|
||||
document.getElementById('btn-proxy-login').disabled = true;
|
||||
document.getElementById('term-status').textContent = 'Starte Terminal...';
|
||||
|
||||
// xterm.js laden falls noch nicht geladen
|
||||
if (typeof Terminal === 'undefined') {
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js';
|
||||
s.onload = () => {
|
||||
const s2 = document.createElement('script');
|
||||
s2.src = 'https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js';
|
||||
s2.onload = () => initTerminal();
|
||||
document.head.appendChild(s2);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
} else {
|
||||
initTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
function initTerminal() {
|
||||
const container = document.getElementById('terminal-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
term = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 13,
|
||||
fontFamily: "'Courier New', monospace",
|
||||
theme: { background: '#000000', foreground: '#E0E0F0' },
|
||||
cols: 80,
|
||||
rows: 20,
|
||||
});
|
||||
|
||||
if (window.FitAddon) {
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(container);
|
||||
fitAddon.fit();
|
||||
} else {
|
||||
term.open(container);
|
||||
}
|
||||
|
||||
// Tastatureingabe → Server
|
||||
term.onData((data) => {
|
||||
const b64 = btoa(unescape(encodeURIComponent(data)));
|
||||
send({ action: 'term_input', data: b64 });
|
||||
});
|
||||
|
||||
term.writeln('\x1b[33mVerbinde mit Proxy-Container...\x1b[0m');
|
||||
send({ action: 'proxy_login' });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -427,11 +427,15 @@ async function testProxy(prompt) {
|
|||
|
||||
// ── Claude Login im Proxy-Container ─────────────────────
|
||||
|
||||
let loginProcess = null;
|
||||
|
||||
function dockerExecStreaming(containerName, cmd, onData, onEnd) {
|
||||
|
||||
// ── Interaktives Terminal (xterm.js ↔ Docker Exec) ──────
|
||||
|
||||
const net = require("net");
|
||||
|
||||
function attachTerminal(clientWs, containerName, cmd) {
|
||||
const createBody = JSON.stringify({
|
||||
AttachStdout: true, AttachStderr: true, AttachStdin: false, Tty: true,
|
||||
AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true,
|
||||
Cmd: Array.isArray(cmd) ? cmd : ["sh", "-c", cmd],
|
||||
});
|
||||
|
||||
|
|
@ -445,83 +449,92 @@ function dockerExecStreaming(containerName, cmd, onData, onEnd) {
|
|||
res.on("data", (c) => data += c);
|
||||
res.on("end", () => {
|
||||
if (res.statusCode !== 201) {
|
||||
onEnd(new Error(`Exec create: HTTP ${res.statusCode}`));
|
||||
clientWs.send(JSON.stringify({ type: "term_error", error: `Exec create failed: HTTP ${res.statusCode}` }));
|
||||
return;
|
||||
}
|
||||
const execId = JSON.parse(data).Id;
|
||||
|
||||
// Exec starten — raw TCP socket fuer bidirektionalen Stream
|
||||
// Docker Exec mit Tty macht ein HTTP Upgrade auf raw stream
|
||||
const startBody = JSON.stringify({ Detach: false, Tty: true });
|
||||
const startReq = http.request({
|
||||
socketPath: "/var/run/docker.sock",
|
||||
path: `/exec/${execId}/start`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(startBody) },
|
||||
}, (sRes) => {
|
||||
// Tty=true: kein Multiplexing, direkt Text
|
||||
sRes.on("data", (chunk) => {
|
||||
const text = chunk.toString("utf-8");
|
||||
if (text.trim()) onData(text);
|
||||
});
|
||||
sRes.on("end", () => onEnd(null));
|
||||
sRes.on("error", (err) => onEnd(err));
|
||||
|
||||
const sock = net.connect({ path: "/var/run/docker.sock" }, () => {
|
||||
// Raw HTTP Request senden (Upgrade-artig)
|
||||
const req = `POST /exec/${execId}/start HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\nConnection: Upgrade\r\nUpgrade: tcp\r\nContent-Length: ${Buffer.byteLength(startBody)}\r\n\r\n${startBody}`;
|
||||
sock.write(req);
|
||||
});
|
||||
|
||||
let headersParsed = false;
|
||||
let headerBuf = "";
|
||||
|
||||
sock.on("data", (chunk) => {
|
||||
if (!headersParsed) {
|
||||
// HTTP Response Header parsen
|
||||
headerBuf += chunk.toString("utf-8");
|
||||
const headerEnd = headerBuf.indexOf("\r\n\r\n");
|
||||
if (headerEnd === -1) return; // Noch nicht komplett
|
||||
|
||||
const headers = headerBuf.slice(0, headerEnd);
|
||||
const remaining = chunk.slice(chunk.length - (headerBuf.length - headerEnd - 4));
|
||||
|
||||
headersParsed = true;
|
||||
|
||||
if (!headers.includes("200") && !headers.includes("101")) {
|
||||
clientWs.send(JSON.stringify({ type: "term_error", error: `Exec start failed: ${headers.split("\r\n")[0]}` }));
|
||||
sock.end();
|
||||
return;
|
||||
}
|
||||
|
||||
log("info", "proxy", "Terminal-Session gestartet");
|
||||
clientWs.send(JSON.stringify({ type: "term_ready" }));
|
||||
clientWs._termSock = sock;
|
||||
|
||||
// Verbleibende Daten nach dem Header
|
||||
if (remaining.length > 0) {
|
||||
clientWs.send(JSON.stringify({ type: "term_data", data: remaining.toString("base64") }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tty=true: Rohdaten direkt durchreichen
|
||||
if (clientWs.readyState === WebSocket.OPEN) {
|
||||
clientWs.send(JSON.stringify({ type: "term_data", data: chunk.toString("base64") }));
|
||||
}
|
||||
});
|
||||
|
||||
sock.on("end", () => {
|
||||
log("info", "proxy", "Terminal-Session beendet");
|
||||
if (clientWs.readyState === WebSocket.OPEN) {
|
||||
clientWs.send(JSON.stringify({ type: "term_exit" }));
|
||||
}
|
||||
clientWs._termSock = null;
|
||||
setTimeout(() => checkProxyAuth(), 1000);
|
||||
});
|
||||
|
||||
sock.on("error", (err) => {
|
||||
log("error", "proxy", `Terminal-Socket-Fehler: ${err.message}`);
|
||||
if (clientWs.readyState === WebSocket.OPEN) {
|
||||
clientWs.send(JSON.stringify({ type: "term_error", error: err.message }));
|
||||
}
|
||||
});
|
||||
|
||||
// Wenn Browser-Client disconnected, Socket schliessen
|
||||
clientWs.on("close", () => {
|
||||
if (sock && !sock.destroyed) sock.end();
|
||||
});
|
||||
startReq.on("error", (err) => onEnd(err));
|
||||
startReq.write(startBody);
|
||||
startReq.end();
|
||||
});
|
||||
});
|
||||
createReq.on("error", (err) => onEnd(err));
|
||||
createReq.on("error", (err) => {
|
||||
clientWs.send(JSON.stringify({ type: "term_error", error: err.message }));
|
||||
});
|
||||
createReq.write(createBody);
|
||||
createReq.end();
|
||||
}
|
||||
|
||||
function stripAnsi(str) {
|
||||
// ANSI escape sequences entfernen (Farben, Cursor, etc.)
|
||||
return str.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "")
|
||||
.replace(/\x1b\][^\x07]*\x07/g, "")
|
||||
.replace(/\x1b[><=][0-9]*/g, "")
|
||||
.replace(/[\x00-\x08\x0e-\x1f]/g, "");
|
||||
}
|
||||
|
||||
function startProxyLogin() {
|
||||
if (loginProcess) {
|
||||
log("warn", "proxy", "Login laeuft bereits");
|
||||
return;
|
||||
function handleTermInput(clientWs, data) {
|
||||
if (clientWs._termSock && !clientWs._termSock.destroyed) {
|
||||
clientWs._termSock.write(Buffer.from(data, "base64"));
|
||||
}
|
||||
|
||||
loginProcess = true;
|
||||
log("info", "proxy", "Starte Claude Login im Proxy-Container...");
|
||||
broadcast({ type: "login_status", status: "starting" });
|
||||
|
||||
// TERM=dumb deaktiviert TUI, echo 1 waehlt Dark Mode bei Theme-Auswahl
|
||||
dockerExecStreaming("aria-proxy", "echo 1 | TERM=dumb NO_COLOR=1 CI=1 claude login 2>&1",
|
||||
(chunk) => {
|
||||
const clean = stripAnsi(chunk).trim();
|
||||
if (!clean) return;
|
||||
|
||||
log("info", "proxy", `[login] ${clean}`);
|
||||
|
||||
// URLs erkennen und hervorheben
|
||||
const urlMatch = clean.match(/https?:\/\/[^\s\]]+/);
|
||||
if (urlMatch) {
|
||||
broadcast({ type: "login_url", url: urlMatch[0], raw: clean });
|
||||
} else {
|
||||
broadcast({ type: "login_output", text: clean });
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
loginProcess = null;
|
||||
if (err) {
|
||||
log("error", "proxy", `Login fehlgeschlagen: ${err.message}`);
|
||||
broadcast({ type: "login_status", status: "error", error: err.message });
|
||||
} else {
|
||||
log("info", "proxy", "Login-Prozess beendet");
|
||||
broadcast({ type: "login_status", status: "done" });
|
||||
// Auth nochmal pruefen
|
||||
setTimeout(() => checkProxyAuth(), 1000);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Credentials manuell einfuegen (von einem Rechner wo Claude eingeloggt ist)
|
||||
|
|
@ -737,7 +750,9 @@ wss.on("connection", (ws) => {
|
|||
} else if (msg.action === "check_proxy_auth") {
|
||||
checkProxyAuth();
|
||||
} else if (msg.action === "proxy_login") {
|
||||
startProxyLogin();
|
||||
attachTerminal(ws, "aria-proxy", "claude login");
|
||||
} else if (msg.action === "term_input") {
|
||||
handleTermInput(ws, msg.data);
|
||||
} else if (msg.action === "write_credentials") {
|
||||
writeProxyCredentials(msg.credentials);
|
||||
} else if (msg.action === "docker_logs") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue