feat: add VNC toggle to dashboard
- Add x11vnc + noVNC container with Traefik reverse proxy - Add /api/vnc_status and toggle_vnc action to FastAPI - Add VNC toggle button to BotVPS dashboard - VNC off by default, controlled via dashboard
This commit is contained in:
@@ -1,32 +1,47 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
vps-agent:
|
vps-agent:
|
||||||
build: .
|
build: .
|
||||||
container_name: vps-ai-agent
|
container_name: vps-ai-agent
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
expose:
|
expose:
|
||||||
- "8001"
|
- "8001"
|
||||||
ports:
|
ports:
|
||||||
- "8001:8001"
|
- "8001:8001"
|
||||||
# Monta as credenciais e o socket do docker para o Bot conseguir comandar a VPS raiz!
|
volumes:
|
||||||
volumes:
|
- .:/app
|
||||||
- .:/app
|
- /var/run/docker.sock:/var/run/docker.sock:rw
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:rw
|
- /:/host_root:ro
|
||||||
- /:/host_root:ro # Acesso em leitura à VPS para análise
|
- ./data:/app/data
|
||||||
- ./data:/app/data # Configs dinâmicas (API Keys, etc)
|
env_file:
|
||||||
env_file:
|
- .env
|
||||||
- .env
|
networks:
|
||||||
networks:
|
- coolify
|
||||||
- coolify
|
- ollama_net
|
||||||
- ollama_net
|
labels:
|
||||||
labels:
|
- "traefik.enable=true"
|
||||||
- "traefik.enable=true"
|
- "traefik.http.routers.vps-agent.rule=Host(`claw.reifonas.cloud`)"
|
||||||
- "traefik.http.routers.vps-agent.rule=Host(`claw.reifonas.cloud`)"
|
- "traefik.http.routers.vps-agent.entrypoints=https"
|
||||||
- "traefik.http.routers.vps-agent.entrypoints=https"
|
- "traefik.http.routers.vps-agent.tls=true"
|
||||||
- "traefik.http.routers.vps-agent.tls=true"
|
- "traefik.http.routers.vps-agent.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.routers.vps-agent.tls.certresolver=letsencrypt"
|
- "traefik.http.services.vps-agent.loadbalancer.server.port=8001"
|
||||||
- "traefik.http.services.vps-agent.loadbalancer.server.port=8001"
|
|
||||||
|
vnc:
|
||||||
|
build: ./vnc
|
||||||
|
container_name: vps-vnc
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6080:6080"
|
||||||
|
volumes:
|
||||||
|
- /tmp/.X11-unix:/tmp/.X11-unix:rw
|
||||||
|
networks:
|
||||||
|
- coolify
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.vnc.rule=Host(`vnc.claw.reifonas.cloud`)"
|
||||||
|
- "traefik.http.routers.vnc.entrypoints=http"
|
||||||
|
- "traefik.http.services.vnc.loadbalancer.server.port=6080"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
coolify:
|
coolify:
|
||||||
|
|||||||
15
main.py
15
main.py
@@ -120,8 +120,23 @@ async def run_action(data: dict, is_auth: bool = Depends(verify_password)):
|
|||||||
return {"status": "success", "message": "Cache do servidor limpo com sucesso."}
|
return {"status": "success", "message": "Cache do servidor limpo com sucesso."}
|
||||||
if action_type == "reboot_vps":
|
if action_type == "reboot_vps":
|
||||||
return {"status": "error", "message": "⚠️ Reboot via Web desabilitado por segurança. Use o terminal SSH."}
|
return {"status": "error", "message": "⚠️ Reboot via Web desabilitado por segurança. Use o terminal SSH."}
|
||||||
|
if action_type == "toggle_vnc":
|
||||||
|
import subprocess
|
||||||
|
result = subprocess.run(["docker", "ps", "-q", "-f", "name=vps-vnc"], capture_output=True, text=True)
|
||||||
|
if result.stdout.strip():
|
||||||
|
subprocess.run(["docker", "stop", "vps-vnc"], capture_output=True)
|
||||||
|
return {"status": "success", "message": "VNC desligado.", "vnc_status": "off"}
|
||||||
|
else:
|
||||||
|
subprocess.run(["docker", "start", "vps-vnc"], capture_output=True)
|
||||||
|
return {"status": "success", "message": "VNC ligado. https://vnc.claw.reifonas.cloud/vnc.html", "vnc_status": "on"}
|
||||||
return {"status": "error", "message": f"Ação {action_type} desconhecida."}
|
return {"status": "error", "message": f"Ação {action_type} desconhecida."}
|
||||||
|
|
||||||
|
@app.get("/api/vnc_status")
|
||||||
|
async def vnc_status():
|
||||||
|
import subprocess
|
||||||
|
result = subprocess.run(["docker", "ps", "-q", "-f", "name=vps-vnc"], capture_output=True, text=True)
|
||||||
|
return {"vnc_status": "on" if result.stdout.strip() else "off"}
|
||||||
|
|
||||||
@app.get("/api/test_llm")
|
@app.get("/api/test_llm")
|
||||||
async def test_llm_latency(is_auth: bool = Depends(verify_password)):
|
async def test_llm_latency(is_auth: bool = Depends(verify_password)):
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
|
|||||||
@@ -767,13 +767,21 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Limpar Cache
|
Limpar Cache
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-danger" onclick="executeAction('reboot_vps')">
|
<button type="button" class="btn btn-danger" onclick="executeAction('reboot_vps')">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
||||||
</svg>
|
</svg>
|
||||||
Reboot VPS
|
Reboot VPS
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<button type="button" class="btn" id="vnc-toggle-btn" onclick="executeAction('toggle_vnc')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
||||||
|
<line x1="8" y1="21" x2="16" y2="21"/>
|
||||||
|
<line x1="12" y1="17" x2="12" y2="21"/>
|
||||||
|
</svg>
|
||||||
|
<span id="vnc-toggle-text">Ligar VNC</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="section-title">Configuração AI</div>
|
<div class="section-title">Configuração AI</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -969,14 +977,31 @@
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initDashboard() {
|
function initDashboard() {
|
||||||
fetchStats();
|
fetchStats();
|
||||||
loadConfig();
|
loadConfig();
|
||||||
loadOrchestratorStatus();
|
loadOrchestratorStatus();
|
||||||
loadLLMModels().then(function() {
|
loadVNCStatus();
|
||||||
loadLLMConfig();
|
loadLLMModels().then(function() {
|
||||||
});
|
loadLLMConfig();
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadVNCStatus() {
|
||||||
|
try {
|
||||||
|
const res = await apiFetch('/api/vnc_status');
|
||||||
|
const data = await res.json();
|
||||||
|
const btn = document.getElementById('vnc-toggle-btn');
|
||||||
|
const txt = document.getElementById('vnc-toggle-text');
|
||||||
|
if (data.vnc_status === 'on') {
|
||||||
|
txt.textContent = 'Desligar VNC';
|
||||||
|
btn.classList.add('btn-success');
|
||||||
|
} else {
|
||||||
|
txt.textContent = 'Ligar VNC';
|
||||||
|
btn.classList.remove('btn-success');
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Theme management
|
// Theme management
|
||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
@@ -1049,11 +1074,12 @@
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
async function executeAction(type) {
|
async function executeAction(type) {
|
||||||
const messages = {
|
const messages = {
|
||||||
reboot_vps: '⚠️ Confirma reboot CRÍTICO da VPS?',
|
reboot_vps: '⚠️ Confirma reboot CRÍTICO da VPS?',
|
||||||
clear_cache: 'Limpar cache do servidor?',
|
clear_cache: 'Limpar cache do servidor?',
|
||||||
restart_bot: 'Reiniciar o agente AI?'
|
restart_bot: 'Reiniciar o agente AI?',
|
||||||
};
|
toggle_vnc: null
|
||||||
|
};
|
||||||
|
|
||||||
if (messages[type] && !confirm(messages[type])) return;
|
if (messages[type] && !confirm(messages[type])) return;
|
||||||
|
|
||||||
|
|||||||
23
vnc/Dockerfile
Normal file
23
vnc/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
x11vnc xvfb \
|
||||||
|
novnc websockify \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN mkdir -p /VNC && cd /VNC \
|
||||||
|
&& ln -sf /usr/share/novnc/vnc.html vnc.html \
|
||||||
|
&& ln -sf /usr/share/novnc/vnc_lite.html vnc_lite.html
|
||||||
|
|
||||||
|
# X11 socket from host (display :10)
|
||||||
|
# Host / is mounted at /host_root by BotVPS compose
|
||||||
|
VOLUME ["/tmp/.X11-unix:/host_x11/X11-unix:rw"]
|
||||||
|
|
||||||
|
ENV DISPLAY=:10
|
||||||
|
|
||||||
|
COPY docker-entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 6080
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
26
vnc/docker-entrypoint.sh
Normal file
26
vnc/docker-entrypoint.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Ensure X11 socket exists and has correct permissions
|
||||||
|
chmod 1777 /tmp/.X11-unix 2>/dev/null || true
|
||||||
|
chmod 666 /tmp/.X11-unix/X10 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start x11vnc — connects to host XFCE on display :10
|
||||||
|
x11vnc \
|
||||||
|
-display :10 \
|
||||||
|
-nopw \
|
||||||
|
-bg \
|
||||||
|
-listen 0.0.0.0 \
|
||||||
|
-xkb \
|
||||||
|
-ncache 10 \
|
||||||
|
-ncache_cr \
|
||||||
|
-forever \
|
||||||
|
-shared \
|
||||||
|
-noshm \
|
||||||
|
-rfbport 5900
|
||||||
|
|
||||||
|
# Start websockify → WebSocket proxy for noVNC
|
||||||
|
exec websockify \
|
||||||
|
--web /usr/share/novnc \
|
||||||
|
0.0.0.0:6080 \
|
||||||
|
localhost:5900
|
||||||
Reference in New Issue
Block a user