game / app.py
habulaj's picture
Update app.py
d58e53a verified
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
import subprocess
import os
import json
from pathlib import Path
import asyncio
import time
from datetime import datetime
import re
app = FastAPI(title="Windows XP RDP")
# Informações sobre o container Windows
WINDOWS_CONTAINER_NAME = "windows-rdp"
WINDOWS_IMAGE = "dockurr/windows:latest"
def get_installation_progress():
"""Tenta detectar o progresso da instalação do Windows"""
progress = {
"stage": "unknown",
"percentage": 0,
"message": "Aguardando inicialização..."
}
# Verificar se há arquivos de log do Windows
storage_path = Path("/storage")
if storage_path.exists():
# Verificar se há arquivo de disco (indica que está criando)
disk_file = storage_path / "disk.qcow2"
if disk_file.exists():
size_mb = disk_file.stat().st_size / (1024 * 1024)
if size_mb < 100:
progress = {
"stage": "downloading",
"percentage": min(int(size_mb / 10), 10),
"message": f"Baixando ISO do Windows XP... ({size_mb:.1f} MB)"
}
elif size_mb < 2000:
progress = {
"stage": "creating_disk",
"percentage": min(10 + int((size_mb - 100) / 20), 30),
"message": f"Criando disco virtual... ({size_mb:.1f} MB)"
}
else:
progress = {
"stage": "installing",
"percentage": min(30 + int((size_mb - 2000) / 100), 90),
"message": f"Instalando Windows XP... ({size_mb:.1f} MB)"
}
# Verificar se a porta 8006 está respondendo (Windows iniciou)
try:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
result = sock.connect_ex(('localhost', 8006))
sock.close()
if result == 0:
progress = {
"stage": "ready",
"percentage": 100,
"message": "Windows XP está pronto!"
}
except:
pass
return progress
@app.get("/", response_class=HTMLResponse)
async def root():
"""Página inicial com logs em tempo real"""
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>Windows XP RDP</title>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="5">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
h1 {
color: #667eea;
text-align: center;
margin-bottom: 30px;
}
.progress-container {
background: #f5f5f5;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
.progress-bar {
width: 100%;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
margin: 10px 0;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.progress-text {
text-align: center;
font-size: 18px;
margin: 10px 0;
font-weight: 500;
}
.stage-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
margin: 10px 0;
}
.stage-downloading { background: #ffc107; color: #000; }
.stage-creating_disk { background: #17a2b8; color: #fff; }
.stage-installing { background: #28a745; color: #fff; }
.stage-ready { background: #28a745; color: #fff; }
.stage-unknown { background: #6c757d; color: #fff; }
.logs-container {
background: #1e1e1e;
color: #d4d4d4;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
max-height: 500px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.log-line {
margin: 2px 0;
padding: 2px 0;
}
.log-time {
color: #858585;
}
.log-info { color: #4ec9b0; }
.log-success { color: #4ec9b0; }
.log-error { color: #f48771; }
.log-warning { color: #dcdcaa; }
.info-box {
background: #f5f5f5;
border-left: 4px solid #667eea;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
}
.button {
display: inline-block;
padding: 12px 24px;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 5px;
margin: 10px 5px;
transition: background 0.3s;
}
.button:hover {
background: #5568d3;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-online { background: #28a745; }
.status-offline { background: #dc3545; }
.status-loading { background: #ffc107; animation: pulse 1s infinite; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body>
<div class="container">
<h1>🖥️ Windows XP RDP</h1>
<div class="progress-container">
<div class="progress-text" id="progress-message">Aguardando inicialização...</div>
<div class="progress-bar">
<div class="progress-fill" id="progress-fill" style="width: 0%">
<span id="progress-percent">0%</span>
</div>
</div>
<div id="stage-badge" class="stage-badge stage-unknown">Aguardando</div>
</div>
<div class="info-box">
<h2>📋 Informações de Conexão</h2>
<p><strong>Versão:</strong> Windows XP</p>
<p><strong>Usuário RDP:</strong> <code>Docker</code></p>
<p><strong>Senha RDP:</strong> <code>admin</code></p>
<p><strong>Porta RDP:</strong> <code>3389</code></p>
<p><strong>Web Viewer:</strong> <a href="/web-viewer" class="button">Abrir Web Viewer</a></p>
<p><strong>Status:</strong> <span id="status-indicator" class="status-indicator status-loading"></span><span id="status-text">Verificando...</span></p>
</div>
<div class="info-box">
<h2>📊 Logs de Instalação</h2>
<div class="logs-container" id="logs-container">
<div class="log-line"><span class="log-time">[Aguardando logs...]</span></div>
</div>
</div>
<div class="info-box">
<h2>ℹ️ Informações</h2>
<ul>
<li>A instalação do Windows XP pode levar <strong>20-40 minutos</strong></li>
<li>Você pode acompanhar o progresso acima</li>
<li>O Web Viewer estará disponível quando o Windows iniciar</li>
<li>Esta página atualiza automaticamente a cada 5 segundos</li>
</ul>
</div>
</div>
<script>
let lastLogTime = null;
function updateProgress() {
fetch('/progress')
.then(r => r.json())
.then(data => {
const progressFill = document.getElementById('progress-fill');
const progressPercent = document.getElementById('progress-percent');
const progressMessage = document.getElementById('progress-message');
const stageBadge = document.getElementById('stage-badge');
progressFill.style.width = data.percentage + '%';
progressPercent.textContent = data.percentage + '%';
progressMessage.textContent = data.message;
// Atualizar badge de estágio
stageBadge.className = 'stage-badge stage-' + data.stage;
const stageNames = {
'downloading': 'Baixando ISO',
'creating_disk': 'Criando Disco',
'installing': 'Instalando',
'ready': 'Pronto!',
'unknown': 'Aguardando'
};
stageBadge.textContent = stageNames[data.stage] || 'Aguardando';
})
.catch(err => console.error('Erro ao atualizar progresso:', err));
}
function updateStatus() {
fetch('/status')
.then(r => r.json())
.then(data => {
const statusIndicator = document.getElementById('status-indicator');
const statusText = document.getElementById('status-text');
if (data.running || data.web_viewer_available) {
statusIndicator.className = 'status-indicator status-online';
statusText.textContent = 'Windows XP está rodando';
} else {
statusIndicator.className = 'status-indicator status-loading';
statusText.textContent = 'Windows XP está inicializando...';
}
})
.catch(err => console.error('Erro ao atualizar status:', err));
}
function addLog(message, type = 'info') {
const logsContainer = document.getElementById('logs-container');
const time = new Date().toLocaleTimeString();
const logLine = document.createElement('div');
logLine.className = 'log-line log-' + type;
logLine.innerHTML = '<span class="log-time">[' + time + ']</span> ' + message;
logsContainer.appendChild(logLine);
logsContainer.scrollTop = logsContainer.scrollHeight;
}
function updateLogs() {
fetch('/logs-stream')
.then(r => r.text())
.then(text => {
if (text && text.trim()) {
const lines = text.split('\n').filter(l => l.trim());
lines.forEach(line => {
if (line && !line.includes('[Aguardando logs...]')) {
let type = 'info';
if (line.toLowerCase().includes('error') || line.toLowerCase().includes('fail')) {
type = 'error';
} else if (line.toLowerCase().includes('success') || line.toLowerCase().includes('ready')) {
type = 'success';
} else if (line.toLowerCase().includes('warn')) {
type = 'warning';
}
addLog(line, type);
}
});
}
})
.catch(err => {
// Silenciar erros de logs
});
}
// Atualizar a cada 2 segundos
setInterval(() => {
updateProgress();
updateStatus();
}, 2000);
// Atualizar logs a cada 5 segundos
setInterval(updateLogs, 5000);
// Atualizar imediatamente
updateProgress();
updateStatus();
updateLogs();
</script>
</body>
</html>
"""
return html_content
@app.get("/progress")
async def get_progress():
"""Retorna o progresso da instalação"""
progress = get_installation_progress()
return progress
@app.get("/status")
async def get_status():
"""Verifica o status do Windows (rodando no mesmo container)"""
try:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex(('localhost', 8006))
sock.close()
web_viewer_available = result == 0
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex(('localhost', 3389))
sock.close()
rdp_available = result == 0
running = web_viewer_available or rdp_available
return {
"running": running,
"web_viewer_available": web_viewer_available,
"rdp_available": rdp_available,
"mode": "direct"
}
except Exception as e:
return {
"running": True,
"web_viewer_available": False,
"rdp_available": False,
"mode": "direct",
"note": "Status não pôde ser verificado, mas o container está ativo"
}
@app.get("/logs-stream")
async def get_logs_stream():
"""Retorna logs do Windows em formato de texto"""
logs = []
# Tentar ler logs do FastAPI
try:
fastapi_log = Path("/tmp/fastapi.log")
if fastapi_log.exists():
with open(fastapi_log, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
logs.extend(lines[-50:]) # Últimas 50 linhas
except:
pass
# Tentar ler logs do sistema
try:
# Verificar processos relacionados ao Windows/QEMU
result = subprocess.run(
["ps", "aux"],
capture_output=True,
text=True,
timeout=2
)
if "qemu" in result.stdout.lower() or "windows" in result.stdout.lower():
logs.append("Windows/QEMU está rodando\n")
except:
pass
# Adicionar informações de progresso
progress = get_installation_progress()
logs.append(f"Status: {progress['message']} ({progress['percentage']}%)\n")
if not logs:
logs.append("Aguardando logs do Windows...\n")
return "".join(logs)
@app.get("/web-viewer")
async def web_viewer():
"""Redireciona para o web viewer do Windows"""
import socket
try:
hostname = socket.gethostname()
except:
hostname = "localhost"
return HTMLResponse(f"""
<!DOCTYPE html>
<html>
<head>
<title>Windows XP Web Viewer</title>
<meta charset="UTF-8">
<style>
body {{
margin: 0;
padding: 0;
background: #000;
}}
iframe {{
width: 100vw;
height: 100vh;
border: none;
}}
.error {{
color: white;
text-align: center;
padding: 50px;
font-family: Arial, sans-serif;
}}
.error a {{
color: #667eea;
text-decoration: none;
}}
</style>
</head>
<body>
<iframe id="viewer" src="http://localhost:8006" allowfullscreen></iframe>
<script>
const currentHost = window.location.hostname;
const currentProtocol = window.location.protocol;
const viewerUrl = currentHost === 'localhost' || currentHost === '127.0.0.1'
? 'http://localhost:8006'
: currentProtocol + '//' + currentHost + ':8006';
const iframe = document.getElementById('viewer');
iframe.src = viewerUrl;
let loadTimeout = setTimeout(() => {{
iframe.style.display = 'none';
document.body.innerHTML = '<div class="error"><h1>Web Viewer não disponível</h1><p>O Windows XP pode ainda estar inicializando.</p><p>Aguarde alguns minutos e tente novamente.</p><p>Verifique o progresso em <a href="/">página inicial</a></p><p><small>Tentando conectar em: ' + viewerUrl + '</small></p></div>';
}}, 10000);
iframe.onload = function() {{
clearTimeout(loadTimeout);
}};
iframe.onerror = function() {{
clearTimeout(loadTimeout);
iframe.style.display = 'none';
document.body.innerHTML = '<div class="error"><h1>Erro ao carregar Web Viewer</h1><p>O Windows XP pode ainda estar inicializando ou a porta 8006 não está acessível.</p><p>Verifique o progresso em <a href="/">página inicial</a></p><p><small>URL tentada: ' + viewerUrl + '</small></p></div>';
}};
</script>
</body>
</html>
""")
@app.get("/info")
async def get_info():
"""Retorna informações sobre o ambiente"""
import socket
try:
hostname = socket.gethostname()
except:
hostname = "localhost"
status = await get_status()
progress = get_installation_progress()
hf_space_host = os.environ.get("SPACE_HOST", None)
return {
"hostname": hostname,
"hf_space_host": hf_space_host,
"status": status,
"progress": progress,
"mode": "direct",
"version": "Windows XP",
"rdp_info": {
"username": "Docker",
"password": "admin",
"port": 3389,
"host": "localhost"
},
"web_viewer": {
"url": "http://localhost:8006",
"note": "No HuggingFace Spaces, use o hostname do Space se localhost não funcionar"
}
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)