feat(panel-v3): integración ECOIN — daemon, wallet info y GUI
- Detección de ecoind via JSON-RPC (puerto 7474, lee ~/.ecoin/ecoin.conf) - Botones INICIAR/DETENER/ABRIR GUI/VER INFO en pestaña ECOIN - ABRIR GUI para ecoind antes de lanzar ecoin-qt (no pueden coexistir) - Compilación automática de ecoin-qt con qmake si no está compilado - Grid en tiempo real: balance ECO, bloques, conexiones, wallet, daemon - ecoin_rpc() helper para llamadas JSON-RPC al daemon - _start_ecoin/_stop_ecoin/_open_ecoin_gui/_ecoin_info en panel.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
68b3ceff8d
commit
fe030f2066
7 changed files with 235 additions and 35 deletions
BIN
INSTALLER_V3/__pycache__/panel.cpython-311.pyc
Normal file
BIN
INSTALLER_V3/__pycache__/panel.cpython-311.pyc
Normal file
Binary file not shown.
BIN
INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png
Normal file
BIN
INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 KiB |
BIN
INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png
Normal file
BIN
INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
9
INSTALLER_V3/icons/hicolor/index.theme
Normal file
9
INSTALLER_V3/icons/hicolor/index.theme
Normal file
|
|
@ -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
|
||||||
|
|
@ -125,6 +125,42 @@ def ecoin_installed() -> bool:
|
||||||
def ecoin_wallet_exists() -> bool:
|
def ecoin_wallet_exists() -> bool:
|
||||||
return (Path.home() / ".ecoin" / "wallet.dat").is_file()
|
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:
|
def node_version() -> str:
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(["node", "--version"],
|
r = subprocess.run(["node", "--version"],
|
||||||
|
|
@ -201,23 +237,38 @@ class OasisPanel:
|
||||||
o_run = oasis_running()
|
o_run = oasis_running()
|
||||||
o_ver = oasis_version()
|
o_ver = oasis_version()
|
||||||
o_node = node_version()
|
o_node = node_version()
|
||||||
|
|
||||||
e_dir = find_ecoin_dir()
|
e_dir = find_ecoin_dir()
|
||||||
e_inst = ecoin_installed()
|
e_inst = ecoin_installed()
|
||||||
e_wall = ecoin_wallet_exists()
|
e_wall = ecoin_wallet_exists()
|
||||||
e_qt = (e_dir / "ecoin" / "ecoin-qt").is_file()
|
e_qt = (e_dir / "ecoin" / "ecoin-qt").is_file()
|
||||||
e_dmn = (e_dir / "ecoin" / "src" / "ecoind").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 = {
|
data = {
|
||||||
"oasis_installed": o_inst,
|
"oasis_installed": o_inst,
|
||||||
"oasis_running": o_run,
|
"oasis_running": o_run,
|
||||||
"oasis_version": o_ver,
|
"oasis_version": o_ver,
|
||||||
"node_version": o_node,
|
"node_version": o_node,
|
||||||
"oasis_dir": str(o_dir) if o_inst else "",
|
"oasis_dir": str(o_dir) if o_inst else "",
|
||||||
"ecoin_installed": e_inst,
|
"ecoin_installed": e_inst,
|
||||||
"ecoin_wallet": e_wall,
|
"ecoin_running": e_run,
|
||||||
"ecoin_qt": e_qt,
|
"ecoin_wallet": e_wall,
|
||||||
"ecoin_daemon": e_dmn,
|
"ecoin_qt": e_qt,
|
||||||
"ecoin_dir": str(e_dir) if e_inst else "",
|
"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)})")
|
GLib.idle_add(self._js, f"updateStatus({json.dumps(data)})")
|
||||||
|
|
||||||
|
|
@ -248,9 +299,6 @@ class OasisPanel:
|
||||||
installer_cmds = {
|
installer_cmds = {
|
||||||
"oasis-install": ["--oasis-install"],
|
"oasis-install": ["--oasis-install"],
|
||||||
"ecoin-install": ["--ecoin-install"],
|
"ecoin-install": ["--ecoin-install"],
|
||||||
"ecoin-gui": ["--ecoin-gui"],
|
|
||||||
"ecoin-wallet": ["--wallet-create"],
|
|
||||||
"ecoin-connect": ["--wallet-connect"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == "oasis-stop":
|
if action == "oasis-stop":
|
||||||
|
|
@ -265,6 +313,22 @@ class OasisPanel:
|
||||||
subprocess.Popen(["xdg-open", "http://localhost:3000"])
|
subprocess.Popen(["xdg-open", "http://localhost:3000"])
|
||||||
GLib.idle_add(self._log, "Abriendo 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:
|
elif action in installer_cmds:
|
||||||
GLib.idle_add(self._js, "switchTab('sistema')")
|
GLib.idle_add(self._js, "switchTab('sistema')")
|
||||||
args = installer_cmds[action]
|
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._log, "[Timeout] OASIS no levantó en 30s")
|
||||||
GLib.idle_add(self._poll)
|
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):
|
def _kill_oasis(self):
|
||||||
GLib.idle_add(self._log, "Deteniendo OASIS...")
|
GLib.idle_add(self._log, "Deteniendo OASIS...")
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -59,27 +59,38 @@ function updateStatus(data) {
|
||||||
document.getElementById('globalDot').dataset.state = state;
|
document.getElementById('globalDot').dataset.state = state;
|
||||||
|
|
||||||
// --- ECOIN ---
|
// --- ECOIN ---
|
||||||
const eCard = document.getElementById('ecoinCard');
|
const eCard = document.getElementById('ecoinCard');
|
||||||
const estate = data.ecoin_installed ? 'running' : 'unknown';
|
eCard.dataset.state = data.ecoin_running ? 'running' : (data.ecoin_installed ? 'stopped' : 'unknown');
|
||||||
eCard.dataset.state = estate;
|
|
||||||
|
|
||||||
if (data.ecoin_installed) {
|
if (data.ecoin_running) {
|
||||||
document.getElementById('ecoinState').textContent = 'COMPILADO';
|
document.getElementById('ecoinState').textContent = 'ACTIVO';
|
||||||
document.getElementById('ecoinSub').textContent = 'wallet ECOIN disponible';
|
document.getElementById('ecoinSub').textContent = 'daemon corriendo (headless)';
|
||||||
_btn('btnEGui', false);
|
_btn('btnEStart', true);
|
||||||
_btn('btnEWallet', false);
|
_btn('btnEStop', false);
|
||||||
_btn('btnEConnect', 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 {
|
} else {
|
||||||
document.getElementById('ecoinState').textContent = 'NO INSTALADO';
|
document.getElementById('ecoinState').textContent = 'NO INSTALADO';
|
||||||
document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar';
|
document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar';
|
||||||
_btn('btnEGui', true);
|
_btn('btnEStart', true);
|
||||||
_btn('btnEWallet', true);
|
_btn('btnEStop', true);
|
||||||
_btn('btnEConnect', true);
|
_btn('btnEGui', true);
|
||||||
|
_btn('btnEInfo', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('eWallet').textContent = data.ecoin_wallet ? 'Si' : 'No';
|
// Info grid ecoin
|
||||||
document.getElementById('eQt').textContent = data.ecoin_qt ? 'Si' : 'No';
|
document.getElementById('eBalance').textContent = data.ecoin_balance != null ? data.ecoin_balance + ' ECO' : '—';
|
||||||
document.getElementById('eDaemon').textContent = data.ecoin_daemon ? 'Si' : 'No';
|
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) ────────────────────────────────
|
// ── Log (llamado desde Python) ────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -72,19 +72,21 @@
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<button class="btn primary" id="btnEInstall" onclick="send('ecoin-install')">⬇ INSTALAR</button>
|
<button class="btn primary" id="btnEStart" onclick="send('ecoin-start')" disabled>▶ INICIAR</button>
|
||||||
<button class="btn" id="btnEGui" onclick="send('ecoin-gui')" disabled>◈ ABRIR GUI</button>
|
<button class="btn" id="btnEStop" onclick="send('ecoin-stop')" disabled>■ DETENER</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<button class="btn" id="btnEWallet" onclick="send('ecoin-wallet')" disabled>✦ CREAR WALLET</button>
|
<button class="btn" id="btnEGui" onclick="send('ecoin-gui')" disabled>◈ ABRIR GUI</button>
|
||||||
<button class="btn" id="btnEConnect" onclick="send('ecoin-connect')" disabled>⟳ CONECTAR</button>
|
<button class="btn" id="btnEInfo" onclick="send('ecoin-info')" disabled>≡ VER INFO</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="infobox">
|
<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">BALANCE</span> <span class="iv" id="eBalance">—</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">BLOQUES</span> <span class="iv" id="eBlocks">—</span></div>
|
||||||
<div class="info-row"><span class="ik">ECOIND</span> <span class="iv" id="eDaemon">—</span></div>
|
<div class="info-row"><span class="ik">CONEXIONES</span> <span class="iv" id="eConns">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">WALLET</span> <span class="iv" id="eWallet">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">ECOIND</span> <span class="iv" id="eDaemon">—</span></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue