fix(panel-v3): detección de puerto, botones y limpieza de warnings

- oasis_running() usa ss en lugar de pgrep (evita falsos positivos)
- _start_oasis() lanza oasis.sh en background y espera el puerto
- INSTALAR se deshabilita cuando OASIS ya está instalado/activo
- Corrige DeprecationWarning: override_background_color → CssProvider
- Corrige DeprecationWarning: run_javascript → evaluate_javascript
- Desactiva caché de WebKit, carga por URI directa
- Inspector de WebKit activado para depuración

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SITO 2026-03-30 12:26:24 +02:00
parent ae79e45c19
commit 67acbf1add
17 changed files with 2692 additions and 0 deletions

110
INSTALLER_V3/ui/app.js Normal file
View file

@ -0,0 +1,110 @@
// =============================================================
// Solar Net Hub — app.js
// Bridge JS ↔ Python y lógica de UI
// =============================================================
// ── Bridge JS → Python ────────────────────────────────────────
function send(action) {
window.webkit.messageHandlers.bridge.postMessage(
JSON.stringify({ action })
);
}
// ── Tabs ──────────────────────────────────────────────────────
function switchTab(name) {
document.querySelectorAll('.tab-panel').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.tabBtn').forEach(el => el.classList.remove('active'));
const panel = document.getElementById('tab-' + name);
const btn = document.querySelector('[data-tab="' + name + '"]');
if (panel) panel.classList.add('active');
if (btn) btn.classList.add('active');
}
// ── Actualizar estado (llamado desde Python) ──────────────────
function updateStatus(data) {
// --- OASIS ---
const oCard = document.getElementById('oasisCard');
const state = data.oasis_running ? 'running'
: data.oasis_installed ? 'stopped'
: 'unknown';
oCard.dataset.state = state;
if (data.oasis_running) {
document.getElementById('oasisState').textContent = 'ACTIVO';
document.getElementById('oasisSub').textContent = 'servidor corriendo en puerto 3000';
_btn('btnStart', true);
_btn('btnStop', false);
_btn('btnInstall', true);
_btn('btnBrowser', false);
} else if (data.oasis_installed) {
document.getElementById('oasisState').textContent = 'INSTALADO';
document.getElementById('oasisSub').textContent = 'servidor detenido';
_btn('btnStart', false);
_btn('btnStop', true);
_btn('btnInstall', true);
_btn('btnBrowser', true);
} else {
document.getElementById('oasisState').textContent = 'NO INSTALADO';
document.getElementById('oasisSub').textContent = 'instala OASIS para comenzar';
_btn('btnStart', true);
_btn('btnStop', true);
_btn('btnInstall', false);
_btn('btnBrowser', true);
}
document.getElementById('iVer').textContent = data.oasis_version ? 'v' + data.oasis_version : '—';
document.getElementById('iNode').textContent = data.node_version || '—';
document.getElementById('iDir').textContent = data.oasis_dir || '—';
// Global dot
document.getElementById('globalDot').dataset.state = state;
// --- ECOIN ---
const eCard = document.getElementById('ecoinCard');
const estate = data.ecoin_installed ? 'running' : 'unknown';
eCard.dataset.state = estate;
if (data.ecoin_installed) {
document.getElementById('ecoinState').textContent = 'COMPILADO';
document.getElementById('ecoinSub').textContent = 'wallet ECOIN disponible';
_btn('btnEGui', false);
_btn('btnEWallet', false);
_btn('btnEConnect', false);
} else {
document.getElementById('ecoinState').textContent = 'NO INSTALADO';
document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar';
_btn('btnEGui', true);
_btn('btnEWallet', true);
_btn('btnEConnect', true);
}
document.getElementById('eWallet').textContent = data.ecoin_wallet ? 'Si' : 'No';
document.getElementById('eQt').textContent = data.ecoin_qt ? 'Si' : 'No';
document.getElementById('eDaemon').textContent = data.ecoin_daemon ? 'Si' : 'No';
}
// ── Log (llamado desde Python) ────────────────────────────────
function appendLog(text) {
const area = document.getElementById('logArea');
const div = document.createElement('div');
div.className = 'log-line';
if (text.startsWith('$')) div.classList.add('cmd');
else if (text.startsWith('──')) div.classList.add('end');
else if (text.startsWith('[Error')) div.classList.add('error');
div.textContent = text;
area.appendChild(div);
area.scrollTop = area.scrollHeight;
}
function clearLog() {
document.getElementById('logArea').innerHTML = '';
}
// ── Helper: deshabilitar/habilitar botón ──────────────────────
function _btn(id, disabled) {
const el = document.getElementById(id);
if (el) el.disabled = disabled;
}

102
INSTALLER_V3/ui/index.html Normal file
View file

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OASIS</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- HEADER -->
<header>
<div class="header-left">
<img src="../oasis-logo.png" class="logo" alt="OASIS">
<div class="header-titles">
<span class="app-title">OASIS</span>
<span class="app-sub">SOLAR NET HUB PANEL</span>
</div>
</div>
<div class="status-dot" id="globalDot" data-state="unknown"></div>
</header>
<!-- TABS -->
<nav class="tabbar">
<button class="tabBtn active" data-tab="oasis" onclick="switchTab('oasis')">OASIS</button>
<button class="tabBtn" data-tab="ecoin" onclick="switchTab('ecoin')">ECOIN</button>
<button class="tabBtn" data-tab="sistema" onclick="switchTab('sistema')">SISTEMA</button>
</nav>
<!-- CONTENIDO -->
<main>
<!-- ── OASIS ── -->
<section class="tab-panel active" id="tab-oasis">
<div class="status-card" id="oasisCard" data-state="unknown">
<div class="card-stripe"></div>
<div class="card-body">
<div class="card-state" id="oasisState">COMPROBANDO</div>
<div class="card-sub" id="oasisSub">iniciando...</div>
</div>
</div>
<div class="actions">
<div class="action-row">
<button class="btn primary" id="btnStart" onclick="send('oasis-start')" disabled>▶ INICIAR</button>
<button class="btn" id="btnStop" onclick="send('oasis-stop')" disabled>■ DETENER</button>
</div>
<div class="action-row">
<button class="btn" id="btnInstall" onclick="send('oasis-install')">⬇ INSTALAR</button>
<button class="btn" id="btnBrowser" onclick="send('oasis-browser')" disabled>◎ ABRIR WEB</button>
</div>
</div>
<div class="infobox">
<div class="info-row"><span class="ik">VERSION</span><span class="iv" id="iVer"></span></div>
<div class="info-row"><span class="ik">NODE.JS</span><span class="iv" id="iNode"></span></div>
<div class="info-row"><span class="ik">RUTA</span> <span class="iv" id="iDir"></span></div>
</div>
</section>
<!-- ── ECOIN ── -->
<section class="tab-panel" id="tab-ecoin">
<div class="status-card" id="ecoinCard" data-state="unknown">
<div class="card-stripe"></div>
<div class="card-body">
<div class="card-state" id="ecoinState">COMPROBANDO</div>
<div class="card-sub" id="ecoinSub">iniciando...</div>
</div>
</div>
<div class="actions">
<div class="action-row">
<button class="btn primary" id="btnEInstall" onclick="send('ecoin-install')">⬇ INSTALAR</button>
<button class="btn" id="btnEGui" onclick="send('ecoin-gui')" disabled>◈ ABRIR GUI</button>
</div>
<div class="action-row">
<button class="btn" id="btnEWallet" onclick="send('ecoin-wallet')" disabled>✦ CREAR WALLET</button>
<button class="btn" id="btnEConnect" onclick="send('ecoin-connect')" disabled>⟳ CONECTAR</button>
</div>
</div>
<div class="infobox">
<div class="info-row"><span class="ik">WALLET</span> <span class="iv" id="eWallet"></span></div>
<div class="info-row"><span class="ik">ECOIN-QT</span><span class="iv" id="eQt"></span></div>
<div class="info-row"><span class="ik">ECOIND</span> <span class="iv" id="eDaemon"></span></div>
</div>
</section>
<!-- ── SISTEMA ── -->
<section class="tab-panel" id="tab-sistema">
<div class="log-header">LOG DE ACTIVIDAD</div>
<div class="log-area" id="logArea"></div>
<button class="btn btn-clear" onclick="clearLog()">LIMPIAR LOG</button>
</section>
</main>
<script src="app.js"></script>
</body>
</html>

347
INSTALLER_V3/ui/style.css Normal file
View file

@ -0,0 +1,347 @@
/* ================================================================
OASIS Panel v3
CSS puro, sin limitaciones GTK. Control total como Electron/web.
================================================================ */
@font-face {
font-family: 'Dune Rise';
src: url('../Dune_Rise.otf') format('opentype');
font-weight: normal;
font-style: normal;
}
:root {
--bg: #000000;
--bg2: #080808;
--bg3: #0D0D0D;
/* OASIS — naranja */
--orange: #FF4E00;
--orange-dim: #882200;
--orange-mute: #441100;
/* ECOIN — amarillo */
--yellow: #FFB300;
--yellow-dim: #886000;
--yellow-mute: #443000;
/* LOG — verde neon */
--neon: #39FF14;
--neon-dim: #1a7a09;
/* General */
--green: #27D980;
--border: #1A1A1A;
--font: 'Dune Rise', 'Cantarell', 'Ubuntu', sans-serif;
--mono: 'DejaVu Sans Mono', 'Courier New', monospace;
}
/* ── Reset ── */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
html, body {
width: 390px;
height: 620px;
overflow: hidden;
background: var(--bg);
color: var(--orange);
font-family: var(--font);
font-size: 10pt;
display: flex;
flex-direction: column;
user-select: none;
}
/* ── Header ─────────────────────────────────────────────────── */
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--bg2);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.header-left {
display: flex;
align-items: center;
gap: 10px;
}
.logo {
width: 38px;
height: 38px;
object-fit: contain;
flex-shrink: 0;
}
.header-titles {
display: flex;
flex-direction: column;
gap: 2px;
}
.app-title {
font-size: 15pt;
color: var(--orange);
letter-spacing: 5px;
line-height: 1;
}
.app-sub {
font-size: 6pt;
color: var(--orange-mute);
letter-spacing: 2px;
}
.status-dot {
font-size: 11pt;
transition: color 0.4s;
line-height: 1;
}
.status-dot[data-state="running"] { color: var(--green); }
.status-dot[data-state="stopped"] { color: var(--orange); }
.status-dot[data-state="unknown"] { color: var(--orange-mute); }
/* ── Tab bar ─────────────────────────────────────────────────── */
nav.tabbar {
display: flex;
background: var(--bg);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.tabBtn {
flex: 1;
background: none;
border: none;
border-bottom: 3px solid transparent;
padding: 11px 0;
font-size: 7.5pt;
letter-spacing: 2px;
font-family: var(--font);
cursor: pointer;
transition: color 0.2s, border-color 0.2s;
}
/* Tab OASIS — naranja */
.tabBtn[data-tab="oasis"] { color: var(--orange-dim); }
.tabBtn[data-tab="oasis"]:hover { color: var(--orange); }
.tabBtn[data-tab="oasis"].active { color: var(--orange); border-bottom-color: var(--orange); }
/* Tab ECOIN — amarillo */
.tabBtn[data-tab="ecoin"] { color: var(--yellow-dim); }
.tabBtn[data-tab="ecoin"]:hover { color: var(--yellow); }
.tabBtn[data-tab="ecoin"].active { color: var(--yellow); border-bottom-color: var(--yellow); }
/* Tab SISTEMA — verde neon */
.tabBtn[data-tab="sistema"] { color: var(--neon-dim); }
.tabBtn[data-tab="sistema"]:hover { color: var(--neon); }
.tabBtn[data-tab="sistema"].active { color: var(--neon); border-bottom-color: var(--neon); }
/* ── Main content ────────────────────────────────────────────── */
main {
flex: 1;
overflow: hidden;
position: relative;
background: var(--bg);
}
.tab-panel {
display: none;
flex-direction: column;
height: 100%;
overflow-y: auto;
background: var(--bg);
}
.tab-panel.active { display: flex; }
/* ── Scrollbar ───────────────────────────────────────────────── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: #222; border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: #333; }
/* ================================================================
OASIS TAB naranja
================================================================ */
#tab-oasis .card-state { color: var(--orange-dim); }
#tab-oasis .status-card[data-state="running"] .card-state { color: var(--green); }
#tab-oasis .status-card[data-state="stopped"] .card-state { color: var(--orange); }
#tab-oasis .status-card[data-state="running"] .card-stripe { background: var(--green); }
#tab-oasis .status-card[data-state="stopped"] .card-stripe { background: var(--orange); }
#tab-oasis .card-sub { color: var(--orange-mute); }
#tab-oasis .btn { color: var(--orange); border-color: var(--orange); }
#tab-oasis .btn.primary { background: var(--orange); color: #000; border-color: var(--orange); }
#tab-oasis .ik { color: var(--orange-dim); }
#tab-oasis .iv { color: var(--orange); }
/* ================================================================
ECOIN TAB amarillo
================================================================ */
#tab-ecoin .card-state { color: var(--yellow-dim); }
#tab-ecoin .status-card[data-state="running"] .card-state { color: var(--yellow); }
#tab-ecoin .status-card[data-state="stopped"] .card-state { color: var(--yellow-dim); }
#tab-ecoin .status-card[data-state="running"] .card-stripe { background: var(--yellow); }
#tab-ecoin .status-card[data-state="stopped"] .card-stripe { background: var(--yellow-dim); }
#tab-ecoin .card-sub { color: var(--yellow-mute); }
#tab-ecoin .btn { color: var(--yellow); border-color: var(--yellow); }
#tab-ecoin .btn.primary { background: var(--yellow); color: #000; border-color: var(--yellow); }
#tab-ecoin .ik { color: var(--yellow-dim); }
#tab-ecoin .iv { color: var(--yellow); }
/* ================================================================
STATUS CARD base
================================================================ */
.status-card {
display: flex;
margin: 14px 14px 6px;
background: var(--bg2);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
flex-shrink: 0;
transition: border-color 0.3s;
}
.card-stripe {
width: 5px;
flex-shrink: 0;
background: #1A1A1A;
transition: background 0.4s;
}
.status-card[data-state="unknown"] .card-stripe { background: #1A1A1A; }
.card-body {
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 5px;
}
.card-state {
font-size: 18pt;
font-weight: bold;
letter-spacing: 2px;
transition: color 0.4s;
}
.card-sub {
font-size: 7.5pt;
letter-spacing: 1px;
}
/* ── Botones base ────────────────────────────────────────────── */
.actions {
padding: 6px 14px 2px;
flex-shrink: 0;
}
.action-row {
display: flex;
gap: 8px;
margin-bottom: 8px;
}
.btn {
flex: 1;
background: var(--bg);
border: 1px solid currentColor;
border-radius: 20px;
padding: 9px 10px;
font-size: 7.5pt;
font-weight: bold;
letter-spacing: 1px;
font-family: var(--font);
cursor: pointer;
transition: background 0.2s, color 0.2s, border-color 0.2s, opacity 0.2s;
white-space: nowrap;
text-align: center;
}
.btn:hover:not(:disabled) {
background: var(--green);
color: #000;
border-color: var(--green);
}
.btn:active:not(:disabled) {
background: #1fa865;
border-color: #1fa865;
color: #000;
}
.btn:disabled {
opacity: 0.2;
cursor: not-allowed;
}
.btn.btn-clear {
flex: none;
width: calc(100% - 28px);
margin: 6px 14px 12px;
color: var(--neon);
border-color: var(--neon);
}
.btn.btn-clear:hover:not(:disabled) {
background: var(--neon);
color: #000;
border-color: var(--neon);
}
/* ── Info box ────────────────────────────────────────────────── */
.infobox {
margin: 8px 14px 14px;
border-top: 1px solid var(--border);
padding-top: 8px;
flex-shrink: 0;
}
.info-row {
display: flex;
align-items: center;
padding: 5px 2px;
border-bottom: 1px solid #080808;
}
.info-row:last-child { border-bottom: none; }
.ik {
font-size: 7.5pt;
letter-spacing: 1px;
min-width: 82px;
flex-shrink: 0;
}
.iv {
font-size: 7.5pt;
word-break: break-all;
}
/* ================================================================
SISTEMA / LOG verde neon
================================================================ */
.log-header {
font-size: 6.5pt;
color: var(--neon-dim);
letter-spacing: 2px;
padding: 12px 16px 6px;
flex-shrink: 0;
}
.log-area {
flex: 1;
background: #030303;
border: 1px solid #0a1a0a;
border-radius: 8px;
margin: 0 14px 8px;
padding: 10px 12px;
overflow-y: auto;
font-family: var(--mono);
font-size: 7.5pt;
color: var(--neon);
line-height: 1.55;
min-height: 0;
}
.log-line { word-break: break-all; padding: 1px 0; color: var(--neon); }
.log-line.cmd { color: var(--orange); }
.log-line.end { color: var(--neon-dim); margin-top: 4px; }
.log-line.error { color: #ff4444; }