diff --git a/INSTALLER_V3/__pycache__/panel.cpython-311.pyc b/INSTALLER_V3/__pycache__/panel.cpython-311.pyc new file mode 100644 index 0000000..f213c4f Binary files /dev/null and b/INSTALLER_V3/__pycache__/panel.cpython-311.pyc differ diff --git a/INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png b/INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png new file mode 100644 index 0000000..4308a29 Binary files /dev/null and b/INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png differ diff --git a/INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png b/INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png new file mode 100644 index 0000000..ccf33ce Binary files /dev/null and b/INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png differ diff --git a/INSTALLER_V3/icons/hicolor/index.theme b/INSTALLER_V3/icons/hicolor/index.theme new file mode 100644 index 0000000..29ece9b --- /dev/null +++ b/INSTALLER_V3/icons/hicolor/index.theme @@ -0,0 +1,9 @@ +[Icon Theme] +Name=Hicolor +Comment=Local fallback for Solar Net Hub +Directories=256x256/apps + +[256x256/apps] +Size=256 +Context=Applications +Type=Fixed diff --git a/INSTALLER_V3/panel.py b/INSTALLER_V3/panel.py index 5c7fc6b..78bf949 100755 --- a/INSTALLER_V3/panel.py +++ b/INSTALLER_V3/panel.py @@ -125,6 +125,42 @@ def ecoin_installed() -> bool: def ecoin_wallet_exists() -> bool: return (Path.home() / ".ecoin" / "wallet.dat").is_file() +def ecoin_rpc_config() -> dict: + """Lee credenciales RPC de ~/.ecoin/ecoin.conf.""" + conf = Path.home() / ".ecoin" / "ecoin.conf" + cfg = {"rpcuser": "", "rpcpassword": "", "rpcport": "7474", "rpchost": "127.0.0.1"} + if conf.is_file(): + for line in conf.read_text().splitlines(): + line = line.strip() + if line.startswith("#") or "=" not in line: + continue + k, v = line.split("=", 1) + k = k.strip() + if k in cfg: + cfg[k] = v.strip() + return cfg + +def ecoin_rpc(method, params=None): + """Llama al RPC de ecoind. Devuelve (result, error).""" + import http.client, base64 as _b64 + cfg = ecoin_rpc_config() + auth = _b64.b64encode(f"{cfg['rpcuser']}:{cfg['rpcpassword']}".encode()).decode() + body = json.dumps({"method": method, "params": params or [], "id": 1}).encode() + try: + conn = http.client.HTTPConnection(cfg["rpchost"], int(cfg["rpcport"]), timeout=3) + conn.request("POST", "/", body, + {"Content-Type": "application/json", + "Authorization": f"Basic {auth}"}) + resp = conn.getresponse() + data = json.loads(resp.read()) + return data.get("result"), data.get("error") + except Exception as e: + return None, str(e) + +def ecoin_running() -> bool: + result, _ = ecoin_rpc("getinfo") + return result is not None + def node_version() -> str: try: r = subprocess.run(["node", "--version"], @@ -201,23 +237,38 @@ class OasisPanel: o_run = oasis_running() o_ver = oasis_version() o_node = node_version() + e_dir = find_ecoin_dir() e_inst = ecoin_installed() e_wall = ecoin_wallet_exists() e_qt = (e_dir / "ecoin" / "ecoin-qt").is_file() e_dmn = (e_dir / "ecoin" / "src" / "ecoind").is_file() + e_run = ecoin_running() + + # Info RPC solo si el daemon está corriendo + e_info = {} + e_balance = None + if e_run: + info, _ = ecoin_rpc("getinfo") + bal, _ = ecoin_rpc("getbalance") + e_info = info or {} + e_balance = bal data = { - "oasis_installed": o_inst, - "oasis_running": o_run, - "oasis_version": o_ver, - "node_version": o_node, - "oasis_dir": str(o_dir) if o_inst else "", - "ecoin_installed": e_inst, - "ecoin_wallet": e_wall, - "ecoin_qt": e_qt, - "ecoin_daemon": e_dmn, - "ecoin_dir": str(e_dir) if e_inst else "", + "oasis_installed": o_inst, + "oasis_running": o_run, + "oasis_version": o_ver, + "node_version": o_node, + "oasis_dir": str(o_dir) if o_inst else "", + "ecoin_installed": e_inst, + "ecoin_running": e_run, + "ecoin_wallet": e_wall, + "ecoin_qt": e_qt, + "ecoin_daemon": e_dmn, + "ecoin_dir": str(e_dir) if e_inst else "", + "ecoin_balance": e_balance, + "ecoin_blocks": e_info.get("blocks", None), + "ecoin_connections": e_info.get("connections", None), } GLib.idle_add(self._js, f"updateStatus({json.dumps(data)})") @@ -248,9 +299,6 @@ class OasisPanel: installer_cmds = { "oasis-install": ["--oasis-install"], "ecoin-install": ["--ecoin-install"], - "ecoin-gui": ["--ecoin-gui"], - "ecoin-wallet": ["--wallet-create"], - "ecoin-connect": ["--wallet-connect"], } if action == "oasis-stop": @@ -265,6 +313,22 @@ class OasisPanel: subprocess.Popen(["xdg-open", "http://localhost:3000"]) GLib.idle_add(self._log, "Abriendo http://localhost:3000 ...") + elif action == "ecoin-start": + GLib.idle_add(self._js, "switchTab('sistema')") + threading.Thread(target=self._start_ecoin, daemon=True).start() + + elif action == "ecoin-stop": + GLib.idle_add(self._js, "switchTab('sistema')") + threading.Thread(target=self._stop_ecoin, daemon=True).start() + + elif action == "ecoin-gui": + GLib.idle_add(self._js, "switchTab('sistema')") + threading.Thread(target=self._open_ecoin_gui, daemon=True).start() + + elif action == "ecoin-info": + GLib.idle_add(self._js, "switchTab('sistema')") + threading.Thread(target=self._ecoin_info, daemon=True).start() + elif action in installer_cmds: GLib.idle_add(self._js, "switchTab('sistema')") args = installer_cmds[action] @@ -324,6 +388,120 @@ class OasisPanel: GLib.idle_add(self._log, "[Timeout] OASIS no levantó en 30s") GLib.idle_add(self._poll) + # ── ECOIN actions ───────────────────────────────────────────────────── + def _start_ecoin(self): + if ecoin_running(): + GLib.idle_add(self._log, "ecoind ya está corriendo.") + GLib.idle_add(self._poll) + return + e_dir = find_ecoin_dir() + ecoind = e_dir / "ecoin" / "src" / "ecoind" + if not ecoind.is_file(): + GLib.idle_add(self._log, f"[Error]: ecoind no encontrado en {ecoind}") + return + GLib.idle_add(self._log, f"$ {ecoind} -daemon") + try: + subprocess.Popen( + [str(ecoind), "-daemon"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + start_new_session=True, + ) + import time + for i in range(15): + time.sleep(1) + if ecoin_running(): + GLib.idle_add(self._log, "ecoind iniciado correctamente.") + GLib.idle_add(self._poll) + return + GLib.idle_add(self._log, "[Timeout] ecoind no respondió en 15s.") + except Exception as e: + GLib.idle_add(self._log, f"[Error]: {e}") + GLib.idle_add(self._poll) + + def _stop_ecoin(self): + GLib.idle_add(self._log, "Deteniendo ecoind...") + result, err = ecoin_rpc("stop") + if err: + GLib.idle_add(self._log, f"[Error RPC]: {err}") + else: + GLib.idle_add(self._log, "ecoind detenido.") + GLib.idle_add(self._poll) + + def _open_ecoin_gui(self): + e_dir = find_ecoin_dir() + ecoin_qt = e_dir / "ecoin" / "ecoin-qt" + src_dir = e_dir / "ecoin" + + if ecoin_qt.is_file(): + # Si ecoind está corriendo, pararlo antes (no pueden coexistir) + if ecoin_running(): + GLib.idle_add(self._log, "Parando ecoind antes de abrir la GUI...") + ecoin_rpc("stop") + import time + for _ in range(10): + time.sleep(1) + if not ecoin_running(): + break + GLib.idle_add(self._log, f"$ {ecoin_qt}") + subprocess.Popen([str(ecoin_qt)], start_new_session=True) + GLib.idle_add(self._poll) + return + + # No compilado — compilar primero + GLib.idle_add(self._log, "ecoin-qt no encontrado. Compilando...") + + # Comprobar que qmake está disponible + qmake = None + for q in ("qmake", "qmake-qt5", "qmake6"): + r = subprocess.run(["which", q], capture_output=True) + if r.returncode == 0: + qmake = q + break + if not qmake: + GLib.idle_add(self._log, "[Error]: qmake no encontrado.") + GLib.idle_add(self._log, "Instala Qt5: sudo apt install qtbase5-dev qt5-qmake") + return + + GLib.idle_add(self._log, f"$ cd {src_dir} && {qmake} && make -j$(nproc)") + try: + proc = subprocess.Popen( + ["bash", "-c", f"cd '{src_dir}' && {qmake} && make -j$(nproc)"], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + ) + for line in iter(proc.stdout.readline, b""): + if self._alive: + GLib.idle_add(self._log, line.decode("utf-8", errors="replace").rstrip()) + proc.wait() + except Exception as e: + GLib.idle_add(self._log, f"[Error compilando]: {e}") + return + + if ecoin_qt.is_file(): + GLib.idle_add(self._log, "Compilación completada. Lanzando ecoin-qt...") + subprocess.Popen([str(ecoin_qt)], start_new_session=True) + else: + GLib.idle_add(self._log, "[Error]: compilación falló. Revisa las dependencias.") + GLib.idle_add(self._log, "Dependencias necesarias:") + GLib.idle_add(self._log, " sudo apt install qtbase5-dev qt5-qmake libboost-all-dev libssl-dev libdb++-dev") + + def _ecoin_info(self): + if not ecoin_running(): + GLib.idle_add(self._log, "ecoind no está corriendo. Inicia el daemon primero.") + return + info, err = ecoin_rpc("getinfo") + if err: + GLib.idle_add(self._log, f"[RPC Error]: {err}") + return + bal, _ = ecoin_rpc("getbalance") + GLib.idle_add(self._log, "── ECOIN INFO ──────────────────") + GLib.idle_add(self._log, f" Balance: {bal} ECO") + GLib.idle_add(self._log, f" Bloques: {info.get('blocks')}") + GLib.idle_add(self._log, f" Conexiones: {info.get('connections')}") + GLib.idle_add(self._log, f" Versión: {info.get('version')}") + GLib.idle_add(self._log, f" Dificultad: {info.get('difficulty')}") + GLib.idle_add(self._log, "────────────────────────────────") + GLib.idle_add(self._poll) + def _kill_oasis(self): GLib.idle_add(self._log, "Deteniendo OASIS...") try: diff --git a/INSTALLER_V3/ui/app.js b/INSTALLER_V3/ui/app.js index 5d61ecc..3fce5ef 100644 --- a/INSTALLER_V3/ui/app.js +++ b/INSTALLER_V3/ui/app.js @@ -59,27 +59,38 @@ function updateStatus(data) { document.getElementById('globalDot').dataset.state = state; // --- ECOIN --- - const eCard = document.getElementById('ecoinCard'); - const estate = data.ecoin_installed ? 'running' : 'unknown'; - eCard.dataset.state = estate; + const eCard = document.getElementById('ecoinCard'); + eCard.dataset.state = data.ecoin_running ? 'running' : (data.ecoin_installed ? 'stopped' : 'unknown'); - 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); + if (data.ecoin_running) { + document.getElementById('ecoinState').textContent = 'ACTIVO'; + document.getElementById('ecoinSub').textContent = 'daemon corriendo (headless)'; + _btn('btnEStart', true); + _btn('btnEStop', false); + _btn('btnEGui', false); + _btn('btnEInfo', false); + } else if (data.ecoin_installed) { + document.getElementById('ecoinState').textContent = 'INSTALADO'; + document.getElementById('ecoinSub').textContent = 'daemon detenido'; + _btn('btnEStart', false); + _btn('btnEStop', true); + _btn('btnEGui', false); + _btn('btnEInfo', true); } else { document.getElementById('ecoinState').textContent = 'NO INSTALADO'; document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar'; - _btn('btnEGui', true); - _btn('btnEWallet', true); - _btn('btnEConnect', true); + _btn('btnEStart', true); + _btn('btnEStop', true); + _btn('btnEGui', true); + _btn('btnEInfo', 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'; + // Info grid ecoin + document.getElementById('eBalance').textContent = data.ecoin_balance != null ? data.ecoin_balance + ' ECO' : '—'; + document.getElementById('eBlocks').textContent = data.ecoin_blocks != null ? data.ecoin_blocks : '—'; + document.getElementById('eConns').textContent = data.ecoin_connections != null ? data.ecoin_connections : '—'; + document.getElementById('eWallet').textContent = data.ecoin_wallet ? 'Si' : 'No'; + document.getElementById('eDaemon').textContent = data.ecoin_daemon ? 'Si' : 'No'; } // ── Log (llamado desde Python) ──────────────────────────────── diff --git a/INSTALLER_V3/ui/index.html b/INSTALLER_V3/ui/index.html index bffd4dc..0e33f73 100644 --- a/INSTALLER_V3/ui/index.html +++ b/INSTALLER_V3/ui/index.html @@ -72,19 +72,21 @@