diff --git a/INSTALLER/Dune_Rise.ttf b/INSTALLER/Dune_Rise.ttf deleted file mode 100644 index 4da6799..0000000 Binary files a/INSTALLER/Dune_Rise.ttf and /dev/null differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-14-05.webm b/INSTALLER/Screencast from 2025-09-25 22-14-05.webm deleted file mode 100644 index c2e0b9d..0000000 Binary files a/INSTALLER/Screencast from 2025-09-25 22-14-05.webm and /dev/null differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-18-23.webm b/INSTALLER/Screencast from 2025-09-25 22-18-23.webm deleted file mode 100644 index 3b4fb5f..0000000 Binary files a/INSTALLER/Screencast from 2025-09-25 22-18-23.webm and /dev/null differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-46-23.webm b/INSTALLER/Screencast from 2025-09-25 22-46-23.webm deleted file mode 100644 index dd274a1..0000000 Binary files a/INSTALLER/Screencast from 2025-09-25 22-46-23.webm and /dev/null differ diff --git a/INSTALLER/gtk.css b/INSTALLER/gtk.css deleted file mode 100644 index d5b065e..0000000 --- a/INSTALLER/gtk.css +++ /dev/null @@ -1,106 +0,0 @@ -/* ========================================================== - SOLAR NET HUB – GTK3 Theme (YAD) - ========================================================== */ - -@define-color bg #000000; -@define-color fg #E6E6E6; -@define-color muted #9AA0A6; -@define-color accent #FF4E00; /* Naranja corporativo */ -@define-color success #27D980; /* Verde OK */ -@define-color field_bg #0B0B0B; -@define-color stroke #202020; - -/* ---------- Tipografía / color global ---------- */ -* { - color: @fg; - font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; - font-size: 12.1pt; /* un pelín más pequeño */ -} - -/* ---------- Fondos de contenedores ---------- */ -window, dialog, grid, box, scrolledwindow, viewport { background-color: @bg; } -grid { padding: 11px 12px 14px 12px; } /* menos padding para que quepa más */ - -/* ---------- Evitar fondos dentro de textos/botones ---------- */ -label, accellabel, -button label, button accellabel, -button * { background: transparent; background-color: transparent; } - -/* ========================================================== - BOTONES: negro + borde/ texto naranja → hover VERDE + texto negro - ========================================================== */ -/* ========================================================== - CTAs (botones de acción) — versión compacta - ========================================================== */ -button { - background: @bg; /* fondo negro */ - color: @accent; /* texto naranja */ - border: 2px solid @accent; /* borde naranja */ - border-radius: 10px; - padding: 5px 5px; /* ↓ padding */ - font-weight: 200; - font-size: 5pt; /* ↓ tamaño de fuente */ - letter-spacing: .2px; - min-width: 100px; /* ↓ ancho mínimo */ - min-height: 20px; /* ↓ alto mínimo */ - margin: 3px 6px; /* ↓ separación vertical */ -} - -/* Hover: botón verde y texto negro (como pediste) */ -button:hover { - background-color: @success; /* verde */ - color: #000; /* texto negro */ - border-color: @success; -} - -button:active { - background-color: shade(@success, 0.85); - color: #000; - border-color: shade(@success, 0.85); -} - -button:disabled { - background-color: #101010; - color: #444; - border-color: #222; -} - -/* Icono dentro del botón (si lo hay), más contenido y pequeño */ -button image { - margin-right: 6px; - -gtk-icon-transform: scale(0.95); -} - -/* ========================================================== - Entradas / listas (por si las usas) - ========================================================== */ -entry, spinbutton, combobox, textview, treeview { - background-color: @field_bg; - color: @fg; - border: 1px solid @stroke; - border-radius: 10px; - padding: 8px 10px; -} -entry:focus, spinbutton:focus, combobox:focus, textview:focus, treeview:focus { - border-color: @accent; -} - -/* ========================================================== - Progreso YAD - ========================================================== */ -trough { background-color: #121212; border-radius: 6px; } -progressbar progress { background-color: @accent; border-radius: 6px; } - -/* ========================================================== - Tooltips - ========================================================== */ -tooltip, tooltip * { - background-color: #111111; - color: @fg; - border: 1px solid @stroke; - border-radius: 8px; - padding: 6px 8px; -} - -/* Labels sueltos dentro del grid con separación mínima */ -grid label { margin: 3px 0 5px; } diff --git a/INSTALLER/gtk_oasis.css b/INSTALLER/gtk_oasis.css deleted file mode 100644 index 1bf6e57..0000000 --- a/INSTALLER/gtk_oasis.css +++ /dev/null @@ -1,42 +0,0 @@ -/* ========================================================== - SOLAR NET HUB – GTK3 Theme (YAD) – OASIS simple (fluido) - ========================================================== */ - -@define-color bg #000000; -@define-color fg #E6E6E6; -@define-color accent #FF4E00; -@define-color accent_hover #27D980; - -/* Tipografía y color global: tamaño moderado (mejor en HiDPI) */ -* { - color: @fg; - font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; - font-size: 11pt; /* antes 13pt */ -} - -window, dialog, grid, box, scrolledwindow, viewport { - background-color: @bg; -} -grid { padding: 16px; } /* un poco menos de padding */ - -/* Botones sin tamaños rígidos: usa padding, no min-width/height */ -button { - background: #000000; - color: @accent; - border: 2px solid @accent; - border-radius: 14px; - padding: 10px 14px; /* sustituye min-width/height por padding */ - margin: 8px 0; - letter-spacing: 0.5px; -} -button:hover { background: @accent_hover; color: #000; } -button:active { background: @accent; color: #000; } - -/* Limpieza de fondos parásitos en textos dentro de botones */ -label, accellabel, -button label, button accellabel, -button * { - background: transparent; - background-color: transparent; -} - diff --git a/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png b/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png deleted file mode 100644 index f28986c..0000000 Binary files a/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png and /dev/null differ diff --git a/INSTALLER/installer.sh.bak.1757923726 b/INSTALLER/installer.sh.bak.1757923726 deleted file mode 100755 index a72bf07..0000000 --- a/INSTALLER/installer.sh.bak.1757923726 +++ /dev/null @@ -1,346 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ========================= -# SOLAR NET HUB - Installer -# ========================= - -# --- Rutas --- -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" -INSTALLER_DIR="$SCRIPT_DIR" -CSS_SOURCE="$INSTALLER_DIR/gtk.css" # (lo añadimos luego) -ICON_OASIS="$INSTALLER_DIR/oasis-logo.webp" -ICON_ECOIN="$INSTALLER_DIR/ecoin.png" - -APPS_DIR="$HOME/.local/share/applications" -DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")" - -# --- Theming local (no toca el sistema) --- -NAME="SolarHubInstaller" # --name/--class (CSS scope) -THEME_NAME="SolarHub" # ~/.themes/SolarHub/gtk-3.0/gtk.css -YAD_THEME="${THEME_NAME}:dark" -ORANGE="#FF4E00" -GREEN="#27D980" -GREY="#b9b9b9" - -# --- Helpers básicos --- -have(){ command -v "$1" >/dev/null 2>&1; } -pkg_install(){ - local pkgs=("$@") - if have apt-get; then - pkexec bash -lc "apt-get update && apt-get install -y ${pkgs[*]}" - elif have pacman; then - pkexec bash -lc "pacman -Sy --noconfirm ${pkgs[*]}" - elif have dnf; then - pkexec bash -lc "dnf install -y ${pkgs[*]}" - elif have zypper; then - pkexec bash -lc "zypper --non-interactive install ${pkgs[*]}" - else - echo "❌ Gestor de paquetes no soportado. Instala manualmente: yad, policykit-1, hicolor-icon-theme, xdg-user-dirs" >&2 - exit 1 - fi -} - - -ensure_theme(){ - local tdir="$HOME/.themes/${THEME_NAME}/gtk-3.0" - mkdir -p "$tdir" - if [ -f "$CSS_SOURCE" ]; then - sed 's/\r$//' "$CSS_SOURCE" > "$tdir/gtk.css" - else - # CSS mínimo por si aún no creaste gtk.css - cat > "$tdir/gtk.css" <<'CSS' -*{background:#000;color:#E6E6E6;font-family:"Cantarell","Ubuntu","DejaVu Sans",sans-serif;font-size:12pt} -button{background:#FF4E00;color:#000;border:none;border-radius:16px;padding:12px 18px;font-weight:800} -button:hover{background:#ff6a26} button:active{background:#e24a00} -CSS - fi -} - -yad_cmd(){ GTK_THEME="$YAD_THEME" yad --name="$NAME" --class="$NAME" "$@"; } - -progress_user(){ # $1=title $2=cmd (como string) - bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ - --width=940 --height=260 --center -} -progress_root(){ # $1=title $2=cmd (string) -> pkexec - pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ - --width=940 --height=260 --center -} - -ensure_launcher(){ - mkdir -p "$APPS_DIR" "$DESKTOP_DIR" - cat > "$APPS_DIR/INSTALLER.desktop" </dev/null || true - chmod +x "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true -} - -# ========= Detección de estado ========= -OASIS_DIR_DEFAULT="${HOME}/oasis" -OASIS_REPO_DEFAULT="https://code.03c8.net/KrakensLab/oasis.git" -OASIS_MODEL_FILE="oasis-42-1-chat.Q4_K_M.gguf" -OASIS_MODEL_TAR="${OASIS_MODEL_FILE}.tar.gz" -OASIS_MODEL_URL="https://solarnethub.com/code/models/${OASIS_MODEL_TAR}" -oasis_installed(){ local d="${1:-$OASIS_DIR_DEFAULT}"; [[ -d "$d/src/server/node_modules" && -f "$d/AI/$OASIS_MODEL_FILE" ]]; } - -ECOIN_DIR_DEFAULT="${HOME}/ecoin" -ECOIN_REPO_DEFAULT="https://github.com/epsylon/ecoin" -ecoin_installed(){ local d="${1:-$ECOIN_DIR_DEFAULT}"; [[ -x "$d/ecoin/ecoin-qt" || -x "$d/ecoin/src/ecoind" ]]; } -ecoin_wallet_exists(){ [ -f "$HOME/.ecoin/wallet.dat" ]; } - -# ========= Comandos OASIS ========= -node_setup_cmd(){ # $1=major (22/18) -cat < "$CONF" </dev/null 2>&1; then - KEY="$(shuf -i 1-2000000000 -n 1)" - else - KEY=$(( (RANDOM<<15) | RANDOM )); KEY=$((KEY%2000000000+1)) - fi - - # --- MITAD IZQUIERDA: OASIS --- - local O_TEXT O_BTN - if oasis_installed; then - O_TEXT="OASIS\nInstalado" - O_BTN="bash -lc '$0 --oasis-start'!$ICON_OASIS!Abrir OASIS" - else - O_TEXT="OASIS\nNo instalado" - O_BTN="bash -lc '$0 --oasis-install'!$ICON_OASIS!Instalar OASIS" - fi - - yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ - ${ICON_OASIS:+--image="$ICON_OASIS"} --image-on-top \ - --text="$O_TEXT" \ - --field=":BTN" "$O_BTN" \ - --no-buttons --buttons-layout=center & - PID_L=$! - - # --- MITAD DERECHA: ECOIN --- - local E_TEXT E_BTN1 E_BTN2 - if ! ecoin_installed; then - E_TEXT="ECOIN\nNo instalado" - E_BTN1="bash -lc '$0 --ecoin-install'!$ICON_ECOIN!Instalar ECOIN" - E_BTN2="" - else - if ecoin_wallet_exists; then - E_TEXT="ECOIN\nCartera detectada" - E_BTN1="bash -lc '$0 --wallet-connect'!$ICON_ECOIN!Conectar cartera" - E_BTN2="bash -lc '$0 --ecoin-gui'!$ICON_ECOIN!Abrir ECOIN GUI" - else - E_TEXT="ECOIN\nSin cartera" - E_BTN1="bash -lc '$0 --wallet-create'!$ICON_ECOIN!Crear cartera" - E_BTN2="" - fi - fi - - if [ -n "$E_BTN2" ]; then - yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ - ${ICON_ECOIN:+--image="$ICON_ECOIN"} --image-on-top \ - --text="$E_TEXT" \ - --field=":BTN" "$E_BTN1" \ - --field=":BTN" "$E_BTN2" \ - --no-buttons --buttons-layout=center & - else - yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ - ${ICON_ECOIN:+--image="$ICON_ECOIN"} --image-on-top \ - --text="$E_TEXT" \ - --field=":BTN" "$E_BTN1" \ - --no-buttons --buttons-layout=center & - fi - PID_R=$! - - sleep 0.35 - yad_cmd --paned --key="$KEY" \ - --title="SOLAR NET HUB" --width=1180 --height=740 --center \ - --button="Cerrar:0" - - kill "$PID_L" "$PID_R" 2>/dev/null || true -} - -# ========= Entrypoint ========= -ensure_basics -ensure_theme -ensure_launcher - -case "${1:-}" in - --oasis-install) oasis_quick_install 22 "$OASIS_REPO_DEFAULT" "$OASIS_DIR_DEFAULT" ;; - --oasis-start) oasis_quick_start "$OASIS_DIR_DEFAULT" ;; - --ecoin-install) ecoin_quick_install "$ECOIN_REPO_DEFAULT" "$ECOIN_DIR_DEFAULT" ;; - --wallet-create) progress_user "ECOIN: Crear cartera" "$(ecoin_create_wallet_cmd)" ;; - --wallet-connect)progress_user "ECOIN: Conectar cartera" "$(ecoin_connect_wallet_cmd)" ;; - --ecoin-gui) progress_user "ECOIN: Wallet (Qt)" "$(ecoin_qt_cmd "$ECOIN_DIR_DEFAULT")" ;; - *) home ;; -esac -ensure_basics(){ - local miss=() - command -v yad >/dev/null 2>&1 || miss+=("yad") - command -v pkexec >/dev/null 2>&1 || miss+=("policykit-1") - command -v xdg-user-dirs-update >/dev/null 2>&1 || miss+=("xdg-user-dirs") - if command -v dpkg >/dev/null 2>&1; then - dpkg -s hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") - elif command -v rpm >/dev/null 2>&1; then - rpm -q hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") - fi - ((${#miss[@]})) && pkg_install "${miss[@]}" -} diff --git a/INSTALLER/installer_oasis.sh b/INSTALLER/installer_oasis.sh deleted file mode 100755 index 6dcd815..0000000 --- a/INSTALLER/installer_oasis.sh +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# ========================= -# OASIS Installer (simple) -# ========================= - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" -: "${SELF:=$SCRIPT_DIR/installer_oasis.sh}" - -INSTALLER_DIR="$SCRIPT_DIR" -CSS_SOURCE="$INSTALLER_DIR/gtk_oasis.css" - -# Imagen/icono -HERO_FILE="$INSTALLER_DIR/oasis-logito.png"; [ -f "$HERO_FILE" ] || HERO_FILE="$INSTALLER_DIR/oasis-logo.png" -WINDOW_ICON="$INSTALLER_DIR/oasis-logo.png" - -# Tema / ventana -NAME="OasisInstaller" -THEME_NAME="SolarOasis" -YAD_THEME="${THEME_NAME}:dark" - -# Repo (se mantiene por compatibilidad con funciones existentes) -OASIS_REPO_DEFAULT="https://code.03c8.net/KrakensLab/oasis.git" - -# Estado persistente -STATE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/oasis-installer" -STATE_FILE="$STATE_DIR/state" -mkdir -p "$STATE_DIR" - -have(){ command -v "$1" >/dev/null 2>&1; } - -# ===== NUEVO: chequeo de npm (único cambio funcional pedido) ===== -npm_ok(){ - # Permite forzar el estado "no instalado" para pruebas: - # OASIS_FORCE_INSTALL=1 ./INSTALLER/installer_oasis.sh - if [ "${OASIS_FORCE_INSTALL:-0}" = "1" ]; then - return 1 - fi - have npm && npm -v >/dev/null 2>&1 -} - -# ---------- Preflight ---------- -preflight(){ - # DISPLAY “de cortesía” - if [ -z "${DISPLAY:-}" ]; then export DISPLAY=":0"; fi - # YAD disponible? - if ! have yad; then - echo "✘ Falta 'yad'. Instálalo y vuelve a ejecutar." >&2 - exit 1 - fi -} - -ensure_basics(){ - local miss=() - have yad || miss+=("yad") - command -v pkexec >/dev/null 2>&1 || miss+=("policykit-1") - have xdg-user-dirs-update || miss+=("xdg-user-dirs") - if command -v dpkg >/dev/null 2>&1; then - dpkg -s hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") - fi - if ((${#miss[@]})); then - if have apt-get; then - if have pkexec; then pkexec bash -lc "apt-get update && apt-get install -y ${miss[*]}"; - else sudo apt-get update && sudo apt-get install -y "${miss[@]}"; fi - fi - fi -} - -ensure_theme(){ - local tdir="$HOME/.themes/${THEME_NAME}/gtk-3.0" - mkdir -p "$tdir" - if [ -f "$CSS_SOURCE" ]; then - sed 's/\r$//' "$CSS_SOURCE" > "$tdir/gtk.css" - else - cat > "$tdir/gtk.css" <<'CSS' -*{background:#000;color:#E6E6E6;font-family:"Dune Rise","Cantarell","Ubuntu","DejaVu Sans",sans-serif;font-size:13pt} -grid{padding:18px} -button{ - border:2px solid #FF4E00; border-radius:14px; background:#000; color:#FF4E00; - min-width:380px; min-height:46px; margin:10px 0; letter-spacing:.5px; -} -button:hover{ background:#27D980; color:#000; } -button:active{ background:#FF4E00; color:#000; } -image{ margin:8px auto 6px auto; } -CSS - fi -} - -# YAD wrapper -yad_cmd(){ GTK_THEME="$YAD_THEME" NO_AT_BRIDGE=1 yad --name="$NAME" --class="$NAME" "$@"; } - -# -------- Detección / estado de OASIS -------- -oasis_is_installed_dir(){ - local d="$1" - [[ -f "$d/oasis.sh" ]] || [[ -f "$d/src/server/server.js" ]] || [[ -d "$d/src/server/node_modules" ]] -} - -save_oasis_dir(){ - local d="$1" - mkdir -p "$STATE_DIR" - { - echo "OASIS_DIR=$d" - date +"SAVED=%Y-%m-%dT%H:%M:%S" - } > "$STATE_FILE" -} - -read_saved_dir(){ - [ -f "$STATE_FILE" ] && sed -n 's/^OASIS_DIR=//p' "$STATE_FILE" | head -n1 || true -} - -guess_oasis_dir(){ - local cands=() - - # 1) Padre del installer (estructura estándar) - cands+=("$SCRIPT_DIR/..") - - # 2) Guardado previo - local saved; saved="$(read_saved_dir || true)" - [ -n "${saved:-}" ] && cands+=("$saved") - - # 3) Candidatas típicas - cands+=("$HOME/COFRE/CODERS/oasis" "$HOME/oasis" "$HOME/Projects/oasis" "$HOME/Documentos/oasis") - - local d - for d in "${cands[@]}"; do - d="$(cd "$d" 2>/dev/null && pwd || true)" - [ -n "$d" ] || continue - if oasis_is_installed_dir "$d"; then - echo "$d"; return 0 - fi - done - echo "$HOME/oasis" -} - -# -------- Comandos auxiliares existentes (sin cambios) -------- -node_setup_cmd(){ cat <<'EOF' -set -e -apt-get update -apt-get install -y git curl ca-certificates tar gnupg -curl -fsSL https://deb.nodesource.com/setup_22.x | bash - -apt-get install -y nodejs -node -v && npm -v -EOF -} - -oasis_install_cmd(){ cat < /tmp/oasis_gui.log 2>&1 & - for i in $(seq 1 30); do - if ss -lnt sport = :$PORT | grep -q "$PORT"; then - echo "Oasis levantado en $PORT" - break - fi - sleep 1 - done -fi - -# Abrir navegador siempre -xdg-open "http://localhost:$PORT" >/dev/null 2>&1 || true -EOF -} - -progress_user(){ bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons --width=700 --height=180 --center; } -progress_root(){ pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons --width=700 --height=180 --center; } - -oasis_quick_install(){ - local dir="$1" - progress_root "OASIS: Node.js 22" "$(node_setup_cmd)" - progress_user "OASIS: Clonar + npm" "$(oasis_install_cmd "$OASIS_REPO_DEFAULT" "$dir")" - save_oasis_dir "$dir" -} - -oasis_quick_start(){ - local dir="$1" - progress_user "OASIS: Abrir" "$(oasis_start_cmd "$dir" 3000)" -} - -# -------- UI -------- -home_oasis_dialog(){ - local OASIS_DIR; OASIS_DIR="$(guess_oasis_dir)" - save_oasis_dir "$OASIS_DIR" - - local O_VER="—" - if [ -f "$OASIS_DIR/src/server/package.json" ]; then - O_VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$OASIS_DIR/src/server/package.json" | head -n1)" - [ -z "$O_VER" ] && O_VER="—" - fi - - # Subcomandos (OPEN se mantiene EXACTO como antes) - local BTN_OPEN_SITE="/bin/bash -lc '$SELF --open-site'" - local BTN_INSTALL="/bin/bash -lc '$SELF --do-install'" - local BTN_OPEN="/bin/bash -lc '$SELF --open-now'" - - # ===== ÚNICO CAMBIO: decidir etiqueta/acción de INSTALL según npm ===== - local INSTALL_LABEL="INSTALL:BTN" - if npm_ok; then - INSTALL_LABEL="ALREADY INSTALLED:BTN" - BTN_INSTALL="/bin/true" - fi - # (No tocamos BTN_OPEN) - - yad_cmd \ - --title="SOLAR NET HUB" --window-icon="$WINDOW_ICON" \ - --center --width=720 --height=600 \ - --image="$HERO_FILE" --image-on-top \ - --form --borders=16 --columns=1 --align=center \ - \ - --field="OASIS PROJECT:BTN" "$BTN_OPEN_SITE" \ - --field="$INSTALL_LABEL" "$BTN_INSTALL" \ - --field="OPEN:BTN" "$BTN_OPEN" \ - --field="VERSION ( $O_VER ):BTN" "/bin/true" \ - \ - --buttons-layout=center \ - --button="CERRAR:0" -} - -# -------- Subcomandos -------- -case "${1:-}" in - --open-site) - xdg-open "https://oasis-project.pub" >/dev/null 2>&1 || true - exit 0 - ;; - --do-install) - preflight; ensure_basics; ensure_theme - # Ejecutar el install.sh del raíz del repo (padre de INSTALLER) - if [ -f "$SCRIPT_DIR/../install.sh" ]; then - /bin/bash -lc "cd \"$SCRIPT_DIR/..\" && bash install.sh" - else - yad_cmd --error --title="OASIS Installer" --text="No se encontró install.sh en $SCRIPT_DIR/.." - exit 1 - fi - # Guardar ruta para OPEN - dir="$(cd "$SCRIPT_DIR/.." && pwd)" - save_oasis_dir "$dir" - exit 0 - ;; - --open-now) - preflight; ensure_basics; ensure_theme - dir="$(read_saved_dir)"; [ -n "${dir:-}" ] || dir="$(guess_oasis_dir)" - save_oasis_dir "$dir" - oasis_quick_start "$dir" - exit 0 - ;; - *) - preflight; ensure_basics; ensure_theme - home_oasis_dialog - ;; -esac diff --git a/INSTALLER/oasis-ecoin.png b/INSTALLER/oasis-ecoin.png deleted file mode 100644 index 4d870e8..0000000 Binary files a/INSTALLER/oasis-ecoin.png and /dev/null differ diff --git a/INSTALLER/oasis-logito.png b/INSTALLER/oasis-logito.png deleted file mode 100644 index f28986c..0000000 Binary files a/INSTALLER/oasis-logito.png and /dev/null differ diff --git a/INSTALLER/panel.py b/INSTALLER/panel.py deleted file mode 100755 index a3cc7ce..0000000 --- a/INSTALLER/panel.py +++ /dev/null @@ -1,695 +0,0 @@ -#!/usr/bin/env python3 -""" -OASIS Control Panel — Solar Net Hub -Panel de control compacto estilo Mullvad para gestionar OASIS y ECOIN en Linux. -""" - -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib, Gdk, GdkPixbuf - -import subprocess -import os -import sys -import threading -import json -import shutil -from pathlib import Path - -# ── Rutas ────────────────────────────────────────────────────────────────── -SCRIPT_DIR = Path(__file__).parent.resolve() -INSTALLER_SH = SCRIPT_DIR / "installer.sh" -OASIS_DIR = Path.home() / "oasis" -ECOIN_DIR = Path.home() / "ecoin" -MODEL_FILE = "oasis-42-1-chat.Q4_K_M.gguf" - -# ── CSS ──────────────────────────────────────────────────────────────────── -CSS = """ -* { - font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; - color: #E6E6E6; -} - -window { - background-color: #000000; -} - -/* Header */ -box.panel-header { - background-color: #080808; - border-bottom: 1px solid #1C1C1C; -} - -label.panel-title { - font-size: 11pt; - font-weight: bold; - color: #FF4E00; - letter-spacing: 3px; -} - -/* Status card */ -box.status-card { - background-color: #0D0D0D; - border: 1px solid #1C1C1C; - border-radius: 12px; -} - -label.status-main { - font-size: 14pt; - font-weight: bold; -} - -label.state-running { color: #27D980; } -label.state-stopped { color: #FF4E00; } -label.state-unknown { color: #555555; } - -label.status-sub { - font-size: 8pt; - color: #666666; -} - -/* Dots */ -label.dot { font-size: 9pt; } -label.dot-running { color: #27D980; } -label.dot-stopped { color: #FF4E00; } -label.dot-unknown { color: #333333; } - -/* Botones */ -button.btn { - background-color: #000000; - color: #FF4E00; - border: 1px solid #FF4E00; - border-radius: 18px; - padding: 8px 16px; - margin: 5px 6px; - font-size: 8pt; - font-weight: bold; - letter-spacing: 1px; - min-width: 130px; -} - -button.btn:hover { - background-color: #27D980; - color: #000000; - border-color: #27D980; -} - -button.btn:active { - background-color: #1fa865; - color: #000000; - border-color: #1fa865; -} - -button.btn:disabled { - background-color: #080808; - color: #2A2A2A; - border-color: #181818; -} - -button.btn-primary { - background-color: #FF4E00; - color: #000000; - border: 1px solid #FF4E00; - border-radius: 18px; - padding: 8px 16px; - margin: 5px 6px; - font-size: 8pt; - font-weight: bold; - letter-spacing: 1px; - min-width: 130px; -} - -button.btn-primary:hover { - background-color: #27D980; - color: #000000; - border-color: #27D980; -} - -button.btn-primary:disabled { - background-color: #301200; - color: #553322; - border-color: #301200; -} - -/* Notebook / Tabs */ -notebook > header { - background-color: #000000; - border-bottom: 1px solid #1C1C1C; - padding: 0px; -} - -notebook > header tabs { - background-color: #000000; -} - -tab { - background-color: #000000; - color: #444444; - border: none; - border-bottom: 2px solid transparent; - padding: 10px 22px; - font-size: 8pt; - letter-spacing: 1px; - font-weight: bold; -} - -tab:checked { - background-color: #000000; - color: #FF4E00; - border-bottom: 2px solid #FF4E00; -} - -tab:hover { - color: #AAAAAA; -} - -/* Info grid */ -label.info-key { - color: #555555; - font-size: 8pt; - min-width: 90px; -} - -label.info-val { - color: #CCCCCC; - font-size: 8pt; -} - -/* Log area */ -textview.log-view { - background-color: #050505; - color: #27D980; - font-family: "DejaVu Sans Mono", "Monospace", monospace; - font-size: 7.5pt; - padding: 8px; -} - -textview.log-view text { - background-color: #050505; - color: #27D980; -} - -scrolledwindow.log-scroll { - border: 1px solid #1C1C1C; - border-radius: 8px; - margin: 4px 14px 8px 14px; -} - -/* Separadores */ -separator { - background-color: #141414; - min-height: 1px; -} -""" - - -# ── Helpers de estado ────────────────────────────────────────────────────── -def oasis_installed(): - return ( - (OASIS_DIR / "src" / "server" / "node_modules").is_dir() - and (OASIS_DIR / "AI" / MODEL_FILE).is_file() - ) - - -def oasis_version(): - pkg = OASIS_DIR / "src" / "server" / "package.json" - if pkg.is_file(): - try: - return json.loads(pkg.read_text()).get("version", "—") - except Exception: - pass - return "—" - - -def oasis_running(): - try: - r = subprocess.run(["pgrep", "-f", "node.*server.js"], - capture_output=True, timeout=2) - return r.returncode == 0 - except Exception: - return False - - -def ecoin_installed(): - return ( - (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() - or (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() - ) - - -def ecoin_wallet_exists(): - return (Path.home() / ".ecoin" / "wallet.dat").is_file() - - -def node_version(): - try: - r = subprocess.run(["node", "--version"], - capture_output=True, text=True, timeout=3) - return r.stdout.strip() - except Exception: - return "—" - - -# ── Panel principal ──────────────────────────────────────────────────────── -class OasisPanel(Gtk.ApplicationWindow): - - def __init__(self, app): - super().__init__(application=app, title="SOLAR NET HUB") - self.set_default_size(390, 610) - self.set_resizable(False) - self.set_position(Gtk.WindowPosition.CENTER) - - self._build_ui() - # Poll de estado cada 3 s - GLib.timeout_add_seconds(3, self._trigger_poll) - self._trigger_poll() - - # ── Construcción UI ─────────────────────────────────────────────────── - def _build_ui(self): - root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - self.add(root) - - root.pack_start(self._make_header(), False, False, 0) - - self.notebook = Gtk.Notebook() - self.notebook.set_show_border(False) - root.pack_start(self.notebook, True, True, 0) - - self.notebook.append_page(self._make_oasis_tab(), self._tab_lbl("OASIS")) - self.notebook.append_page(self._make_ecoin_tab(), self._tab_lbl("ECOIN")) - self.notebook.append_page(self._make_sistema_tab(), self._tab_lbl("SISTEMA")) - - def _tab_lbl(self, txt): - return Gtk.Label(label=txt) - - # ── Header ──────────────────────────────────────────────────────────── - def _make_header(self): - box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - box.get_style_context().add_class("panel-header") - box.set_margin_top(14) - box.set_margin_bottom(14) - box.set_margin_start(18) - box.set_margin_end(18) - - logo = SCRIPT_DIR / "oasis-logo.png" - if logo.is_file(): - try: - pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(logo), 30, 30, True) - box.pack_start(Gtk.Image.new_from_pixbuf(pb), False, False, 0) - except Exception: - pass - - title = Gtk.Label(label="SOLAR NET HUB") - title.get_style_context().add_class("panel-title") - box.pack_start(title, False, False, 0) - - box.pack_start(Gtk.Box(), True, True, 0) # spacer - - self.header_dot = Gtk.Label(label="●") - self.header_dot.get_style_context().add_class("dot") - self.header_dot.get_style_context().add_class("dot-unknown") - box.pack_end(self.header_dot, False, False, 4) - - return box - - # ── Tab OASIS ───────────────────────────────────────────────────────── - def _make_oasis_tab(self): - scroll = Gtk.ScrolledWindow() - scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - scroll.add(box) - - # — Status card — - card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - card.get_style_context().add_class("status-card") - card.set_margin_top(16) - card.set_margin_bottom(4) - card.set_margin_start(14) - card.set_margin_end(14) - card.set_margin_top(14) - for edge in ("top", "bottom", "start", "end"): - getattr(card, f"set_margin_{edge}")(14) - - row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) - row.set_margin_top(14) - row.set_margin_start(16) - row.set_margin_end(16) - - self.oasis_dot = Gtk.Label(label="●") - self.oasis_dot.get_style_context().add_class("dot") - self.oasis_dot.get_style_context().add_class("dot-unknown") - row.pack_start(self.oasis_dot, False, False, 0) - - self.oasis_state_lbl = Gtk.Label(label="Comprobando…") - self.oasis_state_lbl.get_style_context().add_class("status-main") - self.oasis_state_lbl.get_style_context().add_class("state-unknown") - row.pack_start(self.oasis_state_lbl, False, False, 0) - card.pack_start(row, False, False, 0) - - self.oasis_sub_lbl = Gtk.Label(label="") - self.oasis_sub_lbl.get_style_context().add_class("status-sub") - self.oasis_sub_lbl.set_halign(Gtk.Align.START) - self.oasis_sub_lbl.set_margin_start(16) - self.oasis_sub_lbl.set_margin_bottom(14) - card.pack_start(self.oasis_sub_lbl, False, False, 0) - - box.pack_start(card, False, False, 0) - - # — Botones fila 1 — - r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - r1.set_halign(Gtk.Align.CENTER) - r1.set_margin_top(10) - self.btn_o_start = self._btn("▶ INICIAR", self._on_oasis_start, primary=True) - self.btn_o_stop = self._btn("■ DETENER", self._on_oasis_stop) - r1.pack_start(self.btn_o_start, False, False, 0) - r1.pack_start(self.btn_o_stop, False, False, 0) - box.pack_start(r1, False, False, 0) - - # — Botones fila 2 — - r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - r2.set_halign(Gtk.Align.CENTER) - r2.set_margin_bottom(6) - self.btn_o_install = self._btn("⬇ INSTALAR", self._on_oasis_install) - self.btn_o_browser = self._btn("◎ ABRIR WEB", self._on_oasis_browser) - r2.pack_start(self.btn_o_install, False, False, 0) - r2.pack_start(self.btn_o_browser, False, False, 0) - box.pack_start(r2, False, False, 0) - - # — Separator + info — - sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - sep.set_margin_top(10) - box.pack_start(sep, False, False, 0) - - grid = Gtk.Grid() - grid.set_column_spacing(14) - grid.set_row_spacing(6) - grid.set_margin_start(18) - grid.set_margin_end(18) - grid.set_margin_top(10) - grid.set_margin_bottom(14) - self.oasis_ver_val = self._info_row(grid, 0, "Versión") - self.oasis_node_val = self._info_row(grid, 1, "Node.js") - self.oasis_dir_val = self._info_row(grid, 2, "Ruta") - box.pack_start(grid, False, False, 0) - - return scroll - - # ── Tab ECOIN ───────────────────────────────────────────────────────── - def _make_ecoin_tab(self): - scroll = Gtk.ScrolledWindow() - scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - scroll.add(box) - - # — Status card — - card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) - card.get_style_context().add_class("status-card") - for edge in ("top", "bottom", "start", "end"): - getattr(card, f"set_margin_{edge}")(14) - - row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) - row.set_margin_top(14) - row.set_margin_start(16) - row.set_margin_end(16) - - self.ecoin_dot = Gtk.Label(label="●") - self.ecoin_dot.get_style_context().add_class("dot") - self.ecoin_dot.get_style_context().add_class("dot-unknown") - row.pack_start(self.ecoin_dot, False, False, 0) - - self.ecoin_state_lbl = Gtk.Label(label="Comprobando…") - self.ecoin_state_lbl.get_style_context().add_class("status-main") - self.ecoin_state_lbl.get_style_context().add_class("state-unknown") - row.pack_start(self.ecoin_state_lbl, False, False, 0) - card.pack_start(row, False, False, 0) - - self.ecoin_sub_lbl = Gtk.Label(label="") - self.ecoin_sub_lbl.get_style_context().add_class("status-sub") - self.ecoin_sub_lbl.set_halign(Gtk.Align.START) - self.ecoin_sub_lbl.set_margin_start(16) - self.ecoin_sub_lbl.set_margin_bottom(14) - card.pack_start(self.ecoin_sub_lbl, False, False, 0) - - box.pack_start(card, False, False, 0) - - # — Botones fila 1 — - r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - r1.set_halign(Gtk.Align.CENTER) - r1.set_margin_top(10) - self.btn_e_install = self._btn("⬇ INSTALAR", self._on_ecoin_install, primary=True) - self.btn_e_gui = self._btn("◈ ABRIR GUI", self._on_ecoin_gui) - r1.pack_start(self.btn_e_install, False, False, 0) - r1.pack_start(self.btn_e_gui, False, False, 0) - box.pack_start(r1, False, False, 0) - - # — Botones fila 2 — - r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - r2.set_halign(Gtk.Align.CENTER) - r2.set_margin_bottom(6) - self.btn_e_wallet = self._btn("✦ CREAR WALLET", self._on_ecoin_wallet) - self.btn_e_connect = self._btn("⟳ CONECTAR", self._on_ecoin_connect) - r2.pack_start(self.btn_e_wallet, False, False, 0) - r2.pack_start(self.btn_e_connect, False, False, 0) - box.pack_start(r2, False, False, 0) - - # — Separator + info — - sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) - sep.set_margin_top(10) - box.pack_start(sep, False, False, 0) - - grid = Gtk.Grid() - grid.set_column_spacing(14) - grid.set_row_spacing(6) - grid.set_margin_start(18) - grid.set_margin_end(18) - grid.set_margin_top(10) - grid.set_margin_bottom(14) - self.ecoin_wallet_val = self._info_row(grid, 0, "Wallet") - self.ecoin_qt_val = self._info_row(grid, 1, "ecoin-qt") - self.ecoin_daemon_val = self._info_row(grid, 2, "ecoind") - box.pack_start(grid, False, False, 0) - - return scroll - - # ── Tab SISTEMA ─────────────────────────────────────────────────────── - def _make_sistema_tab(self): - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - - hdr = Gtk.Label(label="Log de actividad") - hdr.get_style_context().add_class("info-key") - hdr.set_halign(Gtk.Align.START) - hdr.set_margin_start(18) - hdr.set_margin_top(12) - hdr.set_margin_bottom(4) - box.pack_start(hdr, False, False, 0) - - scroll = Gtk.ScrolledWindow() - scroll.get_style_context().add_class("log-scroll") - scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scroll.set_vexpand(True) - - self.log_view = Gtk.TextView() - self.log_view.get_style_context().add_class("log-view") - self.log_view.set_editable(False) - self.log_view.set_cursor_visible(False) - self.log_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) - self.log_buf = self.log_view.get_buffer() - self._end_mark = self.log_buf.create_mark( - "end", self.log_buf.get_end_iter(), False - ) - scroll.add(self.log_view) - box.pack_start(scroll, True, True, 0) - - btn = self._btn("LIMPIAR LOG", self._on_clear_log) - btn.set_margin_start(18) - btn.set_margin_end(18) - btn.set_margin_top(8) - btn.set_margin_bottom(12) - btn.set_halign(Gtk.Align.CENTER) - box.pack_start(btn, False, False, 0) - - return box - - # ── Widget helpers ──────────────────────────────────────────────────── - def _btn(self, label, cb, primary=False): - b = Gtk.Button(label=label) - b.get_style_context().add_class("btn-primary" if primary else "btn") - b.connect("clicked", cb) - return b - - def _info_row(self, grid, row, key): - k = Gtk.Label(label=key) - k.get_style_context().add_class("info-key") - k.set_halign(Gtk.Align.START) - grid.attach(k, 0, row, 1, 1) - - v = Gtk.Label(label="—") - v.get_style_context().add_class("info-val") - v.set_halign(Gtk.Align.START) - grid.attach(v, 1, row, 1, 1) - return v - - # ── Dot / state helpers ─────────────────────────────────────────────── - def _dot(self, lbl, state): - for c in ("dot-running", "dot-stopped", "dot-unknown"): - lbl.get_style_context().remove_class(c) - lbl.get_style_context().add_class(f"dot-{state}") - - def _state(self, lbl, text, state): - for c in ("state-running", "state-stopped", "state-unknown"): - lbl.get_style_context().remove_class(c) - lbl.set_text(text) - lbl.get_style_context().add_class(f"state-{state}") - - # ── Status polling ──────────────────────────────────────────────────── - def _trigger_poll(self): - threading.Thread(target=self._poll_thread, daemon=True).start() - return True - - def _poll_thread(self): - o_inst = oasis_installed() - o_run = oasis_running() - o_ver = oasis_version() - o_node = node_version() - e_inst = ecoin_installed() - e_wall = ecoin_wallet_exists() - e_qt = (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() - e_dmn = (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() - GLib.idle_add(self._apply_status, - o_inst, o_run, o_ver, o_node, - e_inst, e_wall, e_qt, e_dmn) - - def _apply_status(self, o_inst, o_run, o_ver, o_node, - e_inst, e_wall, e_qt, e_dmn): - # — Header dot — - hstate = "running" if o_run else ("stopped" if o_inst else "unknown") - self._dot(self.header_dot, hstate) - - # — OASIS — - self._dot(self.oasis_dot, "running" if o_run else ("stopped" if o_inst else "unknown")) - if o_run: - self._state(self.oasis_state_lbl, "ACTIVO", "running") - self.oasis_sub_lbl.set_text("servidor en puerto 3000") - self.btn_o_start.set_sensitive(False) - self.btn_o_stop.set_sensitive(True) - self.btn_o_browser.set_sensitive(True) - elif o_inst: - self._state(self.oasis_state_lbl, "INSTALADO", "stopped") - self.oasis_sub_lbl.set_text("servidor detenido") - self.btn_o_start.set_sensitive(True) - self.btn_o_stop.set_sensitive(False) - self.btn_o_browser.set_sensitive(False) - else: - self._state(self.oasis_state_lbl, "NO INSTALADO", "unknown") - self.oasis_sub_lbl.set_text("instala OASIS para comenzar") - self.btn_o_start.set_sensitive(False) - self.btn_o_stop.set_sensitive(False) - self.btn_o_browser.set_sensitive(False) - - self.oasis_ver_val.set_text(f"v{o_ver}" if o_ver != "—" else "—") - self.oasis_node_val.set_text(o_node if o_node != "—" else "no instalado") - self.oasis_dir_val.set_text(str(OASIS_DIR) if o_inst else "—") - - # — ECOIN — - self._dot(self.ecoin_dot, "running" if e_qt else ("stopped" if e_inst else "unknown")) - if e_inst: - self._state(self.ecoin_state_lbl, "COMPILADO", "running") - self.ecoin_sub_lbl.set_text("wallet ECOIN disponible") - self.btn_e_gui.set_sensitive(True) - self.btn_e_wallet.set_sensitive(True) - self.btn_e_connect.set_sensitive(True) - else: - self._state(self.ecoin_state_lbl, "NO INSTALADO", "unknown") - self.ecoin_sub_lbl.set_text("instala ECOIN para comenzar") - self.btn_e_gui.set_sensitive(False) - self.btn_e_wallet.set_sensitive(False) - self.btn_e_connect.set_sensitive(False) - - self.ecoin_wallet_val.set_text("Sí" if e_wall else "No") - self.ecoin_qt_val.set_text("Sí" if e_qt else "No") - self.ecoin_daemon_val.set_text("Sí" if e_dmn else "No") - - # ── Acciones ───────────────────────────────────────────────────────── - def _run(self, *args): - """Lanza installer.sh con args, redirige output al log.""" - cmd = [str(INSTALLER_SH)] + list(args) - self._log(f"$ {' '.join(cmd)}") - threading.Thread(target=self._exec_log, args=(cmd,), daemon=True).start() - GLib.idle_add(self.notebook.set_current_page, 2) - - def _exec_log(self, cmd): - try: - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, bufsize=1 - ) - for line in iter(proc.stdout.readline, ""): - GLib.idle_add(self._log, line.rstrip()) - proc.wait() - GLib.idle_add(self._log, f"── proceso terminado (código {proc.returncode}) ──") - except Exception as e: - GLib.idle_add(self._log, f"[Error]: {e}") - GLib.idle_add(self._trigger_poll) - - def _log(self, text): - self.log_buf.insert(self.log_buf.get_end_iter(), text + "\n") - self.log_view.scroll_to_mark(self._end_mark, 0.0, True, 0.0, 1.0) - return False - - # OASIS - def _on_oasis_start(self, _): self._run("--oasis-start") - def _on_oasis_install(self, _): self._run("--oasis-install") - - def _on_oasis_stop(self, _): - self._log("Deteniendo OASIS…") - threading.Thread(target=self._kill_oasis, daemon=True).start() - - def _kill_oasis(self): - try: - subprocess.run(["pkill", "-f", "node.*server.js"], timeout=5) - GLib.idle_add(self._log, "Servidor OASIS detenido.") - except Exception as e: - GLib.idle_add(self._log, f"[Error al detener]: {e}") - GLib.idle_add(self._trigger_poll) - - def _on_oasis_browser(self, _): - subprocess.Popen(["xdg-open", "http://localhost:3000"]) - self._log("Abriendo http://localhost:3000 …") - - # ECOIN - def _on_ecoin_install(self, _): self._run("--ecoin-install") - def _on_ecoin_gui(self, _): self._run("--ecoin-gui") - def _on_ecoin_wallet(self, _): self._run("--wallet-create") - def _on_ecoin_connect(self, _): self._run("--wallet-connect") - - # Sistema - def _on_clear_log(self, _): - self.log_buf.set_text("") - - -# ── Aplicación ───────────────────────────────────────────────────────────── -class OasisApp(Gtk.Application): - def __init__(self): - super().__init__(application_id="net.solarnethub.panel") - - def do_activate(self): - win = OasisPanel(self) - win.show_all() - - -def main(): - # Cargar CSS - provider = Gtk.CssProvider() - provider.load_from_data(CSS.encode("utf-8")) - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, - ) - - app = OasisApp() - sys.exit(app.run(sys.argv)) - - -if __name__ == "__main__": - main() diff --git a/INSTALLER/Dune_Rise.otf b/INSTALLER_V3/Dune_Rise.otf similarity index 100% rename from INSTALLER/Dune_Rise.otf rename to INSTALLER_V3/Dune_Rise.otf 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/ecoin.png b/INSTALLER_V3/ecoin.png similarity index 100% rename from INSTALLER/ecoin.png rename to INSTALLER_V3/ecoin.png diff --git a/INSTALLER/icons/hicolor/256x256/apps/ecoin.png b/INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png similarity index 100% rename from INSTALLER/icons/hicolor/256x256/apps/ecoin.png rename to INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png diff --git a/INSTALLER/icons/hicolor/256x256/apps/oasis-logo.png b/INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png similarity index 100% rename from INSTALLER/icons/hicolor/256x256/apps/oasis-logo.png rename to INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png diff --git a/INSTALLER/icons/hicolor/index.theme b/INSTALLER_V3/icons/hicolor/index.theme similarity index 100% rename from INSTALLER/icons/hicolor/index.theme rename to INSTALLER_V3/icons/hicolor/index.theme diff --git a/INSTALLER/installer.sh b/INSTALLER_V3/installer.sh similarity index 100% rename from INSTALLER/installer.sh rename to INSTALLER_V3/installer.sh diff --git a/INSTALLER/oasis-logo.png b/INSTALLER_V3/oasis-logo.png similarity index 100% rename from INSTALLER/oasis-logo.png rename to INSTALLER_V3/oasis-logo.png diff --git a/INSTALLER_V3/panel.py b/INSTALLER_V3/panel.py new file mode 100755 index 0000000..78bf949 --- /dev/null +++ b/INSTALLER_V3/panel.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +""" +OASIS Control Panel v3 — WebKit2GTK +UI en HTML/CSS/JS dentro de ventana GTK nativa. +Sin interferencias del tema GTK — control visual total como Electron. +""" + +import gi +gi.require_version('Gtk', '3.0') + +# Intentar WebKit2 4.1 primero, luego 4.0 +_wk = None +for _v in ('4.1', '4.0'): + try: + gi.require_version('WebKit2', _v) + _wk = _v + break + except ValueError: + pass +if _wk is None: + print("ERROR: WebKit2GTK no encontrado. Instala: gir1.2-webkit2-4.0") + raise SystemExit(1) + +from gi.repository import Gtk, WebKit2, GLib, Gdk + +import subprocess +import sys +import signal +import threading +import json +from pathlib import Path + +# ── Rutas base ───────────────────────────────────────────────────────────── +SCRIPT_DIR = Path(__file__).parent.resolve() +INSTALLER_SH = SCRIPT_DIR / "installer.sh" +ECOIN_DIR = Path.home() / "ecoin" + +# Estado persistente (mismo fichero que usa el installer original) +_STATE_FILE = Path.home() / ".config" / "oasis-installer" / "state" + +# Candidatos donde puede estar OASIS instalado +_OASIS_CANDIDATES = [ + Path.home() / "COFRE" / "CODERS" / "oasis", + Path.home() / "oasis", + Path.home() / "Projects" / "oasis", + Path.home() / "Proyectos" / "oasis", + Path.home() / "Documentos" / "oasis", + Path.home() / "Documents" / "oasis", + Path.home() / "src" / "oasis", +] + +# ── Detección OASIS ──────────────────────────────────────────────────────── +def _oasis_valid(d: Path) -> bool: + """Flexible: cualquiera de estas señales indica que OASIS está ahí.""" + return ( + (d / "oasis.sh").is_file() + or (d / "src" / "server" / "server.js").is_file() + or (d / "src" / "server" / "node_modules").is_dir() + ) + +def _read_saved_dir() -> Path | None: + """Lee la ruta guardada por el installer original.""" + if _STATE_FILE.is_file(): + for line in _STATE_FILE.read_text().splitlines(): + if line.startswith("OASIS_DIR="): + p = Path(line[10:].strip()) + if p.is_dir(): + return p + return None + +def find_oasis_dir() -> Path: + """Devuelve la ruta de OASIS encontrada o ~/oasis como fallback.""" + candidates = [] + saved = _read_saved_dir() + if saved: + candidates.append(saved) + candidates.extend(_OASIS_CANDIDATES) + for d in candidates: + if d.is_dir() and _oasis_valid(d): + return d + return Path.home() / "oasis" + +def oasis_installed() -> bool: + return _oasis_valid(find_oasis_dir()) + +def oasis_version() -> str: + pkg = find_oasis_dir() / "src" / "server" / "package.json" + if pkg.is_file(): + try: + return json.loads(pkg.read_text()).get("version", "") + except Exception: + pass + return "" + +def oasis_running(port: int = 3000) -> bool: + """Comprueba si hay algo escuchando en el puerto de OASIS (igual que el installer).""" + try: + r = subprocess.run( + ["ss", "-lnt", f"sport = :{port}"], + capture_output=True, text=True, timeout=2 + ) + return str(port) in r.stdout + except Exception: + return False + +# ── Detección ECOIN ──────────────────────────────────────────────────────── +_ECOIN_CANDIDATES = [ + Path.home() / "ecoin", + Path.home() / "COFRE" / "CODERS" / "ecoin", + Path.home() / "Projects" / "ecoin", +] + +def find_ecoin_dir() -> Path: + for d in _ECOIN_CANDIDATES: + if (d / "ecoin" / "ecoin-qt").is_file() or \ + (d / "ecoin" / "src" / "ecoind").is_file(): + return d + return Path.home() / "ecoin" + +def ecoin_installed() -> bool: + d = find_ecoin_dir() + return (d / "ecoin" / "ecoin-qt").is_file() or \ + (d / "ecoin" / "src" / "ecoind").is_file() + +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"], + capture_output=True, text=True, timeout=3) + return r.stdout.strip() + except Exception: + return "" + + +# ── Panel principal ──────────────────────────────────────────────────────── +class OasisPanel: + + def __init__(self): + self._alive = True + self._oasis_proc = None # proceso OASIS activo + + self.win = Gtk.Window() + self.win.set_title("SOLAR NET HUB") + self.win.set_default_size(390, 620) + self.win.set_resizable(False) + self.win.set_position(Gtk.WindowPosition.CENTER) + self.win.connect("destroy", self._on_destroy) + + # Fondo negro en la ventana GTK para evitar parpadeo blanco al cargar + css = b"window { background-color: #000000; }" + provider = Gtk.CssProvider() + provider.load_from_data(css) + self.win.get_style_context().add_provider( + provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ) + + # ── WebKit2 ─────────────────────────────────────────────────────── + settings = WebKit2.Settings() + settings.set_enable_javascript(True) + settings.set_allow_file_access_from_file_urls(True) + settings.set_allow_universal_access_from_file_urls(True) + settings.set_enable_page_cache(False) + + self.webview = WebKit2.WebView() + self.webview.set_settings(settings) + self.webview.set_background_color(Gdk.RGBA(0, 0, 0, 1)) + + # Bridge JS→Python: interceptar navegaciones a oasis://accion + self.webview.connect("decide-policy", self._on_policy) + + self.win.add(self.webview) + + html_file = SCRIPT_DIR / "ui" / "index.html" + self.webview.load_uri(f"file://{html_file}") + + self.win.show_all() + + # Poll inicial tras 1.5 s (tiempo para que cargue el HTML) + GLib.timeout_add(1500, self._initial_poll) + # Poll continuo cada 3 s + GLib.timeout_add_seconds(3, self._poll) + + def _on_destroy(self, *_): + self._alive = False + Gtk.main_quit() + + # ── Polling ─────────────────────────────────────────────────────────── + def _initial_poll(self): + self._poll() + return False + + def _poll(self): + threading.Thread(target=self._poll_thread, daemon=True).start() + return True + + def _poll_thread(self): + o_dir = find_oasis_dir() + o_inst = _oasis_valid(o_dir) + 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_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)})") + + # ── JS helpers ──────────────────────────────────────────────────────── + def _js(self, code): + if self._alive: + self.webview.run_javascript(code, None, None, None) + return False + + def _log(self, text): + if self._alive: + self._js(f"appendLog({json.dumps(text)})") + + # ── Bridge JS→Python via navegación oasis://accion ─────────────────── + def _on_policy(self, webview, decision, decision_type): + if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION: + uri = decision.get_navigation_action().get_request().get_uri() + if uri.startswith("oasis://"): + action = uri[len("oasis://"):] + print(f"[bridge] action={action}", flush=True) + GLib.idle_add(self._dispatch, action) + decision.ignore() + return True + decision.use() + return False + + def _dispatch(self, action): + installer_cmds = { + "oasis-install": ["--oasis-install"], + "ecoin-install": ["--ecoin-install"], + } + + if action == "oasis-stop": + threading.Thread(target=self._kill_oasis, daemon=True).start() + GLib.idle_add(self._js, "switchTab('sistema')") + + elif action == "oasis-start": + GLib.idle_add(self._js, "switchTab('sistema')") + threading.Thread(target=self._start_oasis, daemon=True).start() + + elif action == "oasis-browser": + 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] + threading.Thread(target=self._run_cmd, args=(args,), daemon=True).start() + + def _run_cmd(self, args): + cmd = [str(INSTALLER_SH)] + args + GLib.idle_add(self._log, "$ " + " ".join(cmd)) + try: + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 + ) + for line in iter(proc.stdout.readline, ""): + line = line.rstrip() + if line: + GLib.idle_add(self._log, line) + proc.wait() + GLib.idle_add(self._log, f"── fin (código {proc.returncode}) ──") + except Exception as e: + GLib.idle_add(self._log, f"[Error]: {e}") + GLib.idle_add(self._poll) + + def _start_oasis(self): + import time + oasis_dir = find_oasis_dir() + GLib.idle_add(self._log, f"$ cd {oasis_dir} && bash oasis.sh") + try: + proc = subprocess.Popen( + ["bash", "-lc", f"cd '{oasis_dir}' && bash oasis.sh"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + start_new_session=True, + ) + except Exception as e: + GLib.idle_add(self._log, f"[Error]: {e}") + return + + self._oasis_proc = proc + GLib.idle_add(self._log, f"PID {proc.pid} — esperando puerto 3000...") + + # Leer salida del proceso y mostrarla en el log + def _read_output(): + for line in iter(proc.stdout.readline, b""): + if self._alive: + GLib.idle_add(self._log, line.decode("utf-8", errors="replace").rstrip()) + threading.Thread(target=_read_output, daemon=True).start() + + # Esperar hasta 30s a que el puerto esté activo y abrir navegador + for i in range(30): + time.sleep(1) + if oasis_running(): + GLib.idle_add(self._log, "Puerto 3000 activo — abriendo navegador") + subprocess.Popen(["xdg-open", "http://localhost:3000"]) + GLib.idle_add(self._poll) + return + 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: + import os, signal as sig + if self._oasis_proc and self._oasis_proc.poll() is None: + # Matar el grupo de procesos completo (bash -lc + node hijo) + os.killpg(os.getpgid(self._oasis_proc.pid), sig.SIGTERM) + self._oasis_proc.wait(timeout=5) + self._oasis_proc = None + GLib.idle_add(self._log, "OASIS detenido.") + else: + # Fallback: matar cualquier node backend.js corriendo + subprocess.run(["pkill", "-f", "node.*backend.js"], timeout=5) + GLib.idle_add(self._log, "OASIS detenido.") + except Exception as e: + GLib.idle_add(self._log, f"[Error al detener]: {e}") + GLib.idle_add(self._poll) + + +# ── Main ─────────────────────────────────────────────────────────────────── +def main(): + signal.signal(signal.SIGINT, signal.SIG_DFL) + OasisPanel() + Gtk.main() + + +if __name__ == "__main__": + main() diff --git a/INSTALLER_V3/ui/app.js b/INSTALLER_V3/ui/app.js new file mode 100644 index 0000000..3fce5ef --- /dev/null +++ b/INSTALLER_V3/ui/app.js @@ -0,0 +1,119 @@ +// ============================================================= +// Solar Net Hub — app.js +// Bridge JS ↔ Python y lógica de UI +// ============================================================= + +// ── Bridge JS → Python ──────────────────────────────────────── +function send(action) { + window.location.href = 'oasis://' + 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'); + eCard.dataset.state = data.ecoin_running ? 'running' : (data.ecoin_installed ? 'stopped' : 'unknown'); + + 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('btnEStart', true); + _btn('btnEStop', true); + _btn('btnEGui', true); + _btn('btnEInfo', true); + } + + // 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) ──────────────────────────────── +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; +} diff --git a/INSTALLER_V3/ui/index.html b/INSTALLER_V3/ui/index.html new file mode 100644 index 0000000..0e33f73 --- /dev/null +++ b/INSTALLER_V3/ui/index.html @@ -0,0 +1,104 @@ + + + + + + OASIS + + + + + +
+
+ +
+ OASIS + SOLAR NET HUB PANEL +
+
+
+
+ + + + + +
+ + +
+ +
+
+
+
COMPROBANDO
+
iniciando...
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
VERSION
+
NODE.JS
+
RUTA
+
+
+ + +
+ +
+
+
+
COMPROBANDO
+
iniciando...
+
+
+ +
+
+ + +
+
+ + +
+
+ +
+
BALANCE
+
BLOQUES
+
CONEXIONES
+
WALLET
+
ECOIND
+
+
+ + +
+
LOG DE ACTIVIDAD
+
+ +
+ +
+ + + + diff --git a/INSTALLER_V3/ui/style.css b/INSTALLER_V3/ui/style.css new file mode 100644 index 0000000..14b7fc8 --- /dev/null +++ b/INSTALLER_V3/ui/style.css @@ -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; } diff --git a/start_panel.sh b/start_panel.sh deleted file mode 100755 index 7a39f23..0000000 --- a/start_panel.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -# ============================================================= -# SOLAR NET HUB — Panel de control (lanzador) -# Lanza panel.py si python3-gi está disponible; -# si no, lo instala y reintenta. -# ============================================================= -set -euo pipefail - -REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" -PANEL_PY="$REPO_DIR/INSTALLER/panel.py" -INSTALLER_SH="$REPO_DIR/INSTALLER/installer.sh" - -C_RESET='\033[0m' -C_OK='\033[1;32m' -C_FAIL='\033[1;31m' -C_WARN='\033[1;33m' -C_INFO='\033[1;36m' - -have(){ command -v "$1" >/dev/null 2>&1; } - -# ── Instalar fuente Dune Rise (usuario, sin root) ───────────────────────── -install_font(){ - local src="$REPO_DIR/INSTALLER/Dune_Rise.otf" - local dst="$HOME/.local/share/fonts/Dune_Rise.otf" - if [ -f "$src" ] && [ ! -f "$dst" ]; then - mkdir -p "$HOME/.local/share/fonts" - cp -f "$src" "$dst" - fc-cache -f >/dev/null 2>&1 || true - fi -} - -# ── Verificar / instalar python3-gi ────────────────────────────────────── -check_gi(){ - python3 -c "import gi; gi.require_version('Gtk','3.0'); from gi.repository import Gtk" \ - >/dev/null 2>&1 -} - -install_gi(){ - echo -e "${C_WARN}⚠ python3-gi no encontrado. Instalando…${C_RESET}" - if have apt-get; then sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 - elif have pacman; then sudo pacman -Sy --noconfirm python-gobject - elif have dnf; then sudo dnf install -y python3-gobject gtk3 - elif have zypper; then sudo zypper --non-interactive install python3-gobject gtk3 - else - echo -e "${C_FAIL}✘ Gestor de paquetes no soportado.${C_RESET}" - echo " Instala manualmente: python3-gi / python-gobject" - exit 1 - fi -} - -# ── Verificar DISPLAY / Wayland ─────────────────────────────────────────── -check_display(){ - if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then - echo -e "${C_FAIL}✘ No hay sesión gráfica (DISPLAY/WAYLAND_DISPLAY vacíos).${C_RESET}" - echo " Ejecuta este script desde tu escritorio o una terminal gráfica." - exit 1 - fi -} - -# ── Main ───────────────────────────────────────────────────────────────── -echo -e "${C_INFO}== SOLAR NET HUB :: Panel de Control ==${C_RESET}" - -check_display - -install_font 2>/dev/null || true - -if ! check_gi; then - install_gi - if ! check_gi; then - echo -e "${C_FAIL}✘ No se pudo cargar python3-gi tras la instalación.${C_RESET}" - echo " Intenta cerrar sesión y volver a entrar, o instala python3-gi manualmente." - exit 1 - fi -fi - -echo -e "${C_OK}✔ python3-gi listo. Lanzando panel…${C_RESET}" -exec python3 "$PANEL_PY" "$@" diff --git a/start_panel_v3.sh b/start_panel_v3.sh new file mode 100755 index 0000000..972e320 --- /dev/null +++ b/start_panel_v3.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# ============================================================= +# SOLAR NET HUB — Panel v3 (WebKit2GTK) +# ============================================================= +set -euo pipefail + +REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PANEL_PY="$REPO_DIR/INSTALLER_V3/panel.py" +FONT_SRC="$REPO_DIR/INSTALLER_V3/Dune_Rise.otf" +ICON_SRC="$REPO_DIR/INSTALLER_V3/oasis-logo.png" +APPS_DIR="$HOME/.local/share/applications" +ICONS_DIR="$HOME/.local/share/icons/hicolor/256x256/apps" + +C_OK='\033[1;32m'; C_FAIL='\033[1;31m'; C_WARN='\033[1;33m' +C_INFO='\033[1;36m'; C_RESET='\033[0m' + +have(){ command -v "$1" >/dev/null 2>&1; } + +check_display(){ + [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ] && return + echo -e "${C_FAIL}✘ Sin sesión gráfica.${C_RESET}"; exit 1 +} + +install_font(){ + local dst="$HOME/.local/share/fonts/Dune_Rise.otf" + [ -f "$FONT_SRC" ] && [ ! -f "$dst" ] && { + mkdir -p "$HOME/.local/share/fonts" + cp -f "$FONT_SRC" "$dst" + fc-cache -f >/dev/null 2>&1 || true + echo -e "${C_OK}✔ Fuente Dune Rise instalada.${C_RESET}" + } +} + +check_webkit(){ + python3 -c " +import gi +for v in ('4.1','4.0'): + try: + gi.require_version('WebKit2', v) + from gi.repository import WebKit2 + print(v) + break + except Exception: + pass +" 2>/dev/null +} + +install_webkit(){ + echo -e "${C_WARN}⚠ WebKit2GTK no encontrado. Instalando...${C_RESET}" + if have apt-get; then + sudo apt-get install -y python3-gi python3-gi-cairo \ + gir1.2-webkit2-4.0 libwebkit2gtk-4.0-dev 2>/dev/null || \ + sudo apt-get install -y python3-gi python3-gi-cairo \ + gir1.2-webkit2-4.1 libwebkit2gtk-4.1-dev 2>/dev/null + elif have pacman; then + sudo pacman -Sy --noconfirm webkit2gtk python-gobject + elif have dnf; then + sudo dnf install -y python3-gobject webkit2gtk4.0 + elif have zypper; then + sudo zypper --non-interactive install python3-gobject webkit2gtk3 + else + echo -e "${C_FAIL}✘ Instala manualmente: gir1.2-webkit2-4.0${C_RESET}" + exit 1 + fi +} + +register_desktop(){ + mkdir -p "$APPS_DIR" "$ICONS_DIR" + [ -f "$ICON_SRC" ] && cp -f "$ICON_SRC" "$ICONS_DIR/oasis.png" + + # Borrar entradas antiguas con nombres distintos + rm -f "$APPS_DIR/solarnethub.desktop" \ + "$APPS_DIR/solarnethub-panel.desktop" \ + "$APPS_DIR/INSTALLER.desktop" \ + "$APPS_DIR/oasis.desktop" + + cat > "$APPS_DIR/oasis-panel.desktop" </dev/null || true + + have update-desktop-database && \ + update-desktop-database "$APPS_DIR" >/dev/null 2>&1 || true + have gtk-update-icon-cache && \ + gtk-update-icon-cache -f "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true + + echo -e "${C_OK}✔ Registrado en el menú como 'Solar Net Hub'${C_RESET}" +} + +# ── Main ────────────────────────────────────────────────────── +echo -e "${C_INFO}== SOLAR NET HUB :: Panel v3 (WebKit2GTK) ==${C_RESET}" + +check_display +install_font 2>/dev/null || true +register_desktop 2>/dev/null || true + +WK_VER="$(check_webkit)" +if [ -z "$WK_VER" ]; then + install_webkit + WK_VER="$(check_webkit)" + [ -z "$WK_VER" ] && { + echo -e "${C_FAIL}✘ WebKit2GTK no disponible tras instalación.${C_RESET}" + exit 1 + } +fi + +echo -e "${C_OK}✔ WebKit2 $WK_VER listo. Lanzando panel...${C_RESET}" +exec python3 "$PANEL_PY" "$@"