fix(panel-v3): detección de puerto, botones y limpieza de warnings
- oasis_running() usa ss en lugar de pgrep (evita falsos positivos) - _start_oasis() lanza oasis.sh en background y espera el puerto - INSTALAR se deshabilita cuando OASIS ya está instalado/activo - Corrige DeprecationWarning: override_background_color → CssProvider - Corrige DeprecationWarning: run_javascript → evaluate_javascript - Desactiva caché de WebKit, carga por URI directa - Inspector de WebKit activado para depuración Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ae79e45c19
commit
67acbf1add
17 changed files with 2692 additions and 0 deletions
BIN
INSTALLER_V2/Dune_Rise.otf
Normal file
BIN
INSTALLER_V2/Dune_Rise.otf
Normal file
Binary file not shown.
BIN
INSTALLER_V2/ecoin.png
Normal file
BIN
INSTALLER_V2/ecoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 KiB |
400
INSTALLER_V2/installer.sh
Executable file
400
INSTALLER_V2/installer.sh
Executable file
|
|
@ -0,0 +1,400 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# SOLAR NET HUB - Installer
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# --- Rutas ---
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
|
: "${SELF:=$SCRIPT_DIR/installer.sh}" # ruta absoluta a este script
|
||||||
|
INSTALLER_DIR="$SCRIPT_DIR"
|
||||||
|
CSS_SOURCE="$INSTALLER_DIR/gtk.css"
|
||||||
|
HERO_IMAGE="$INSTALLER_DIR/oasis-ecoin.png" # imagen con los dos logos (head)
|
||||||
|
|
||||||
|
# ICONOS INDIVIDUALES (se usan en ensure_local_icons y en el .desktop)
|
||||||
|
ICON_OASIS="$INSTALLER_DIR/oasis-logo.png"
|
||||||
|
ICON_ECOIN="$INSTALLER_DIR/ecoin.png"
|
||||||
|
|
||||||
|
APPS_DIR="$HOME/.local/share/applications"
|
||||||
|
DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")"
|
||||||
|
|
||||||
|
# --- Tema local (no toca sistema) ---
|
||||||
|
NAME="SolarHubInstaller"
|
||||||
|
THEME_NAME="SolarHub"
|
||||||
|
YAD_THEME="${THEME_NAME}:dark"
|
||||||
|
ORANGE="#FF4E00"
|
||||||
|
GREEN="#27D980"
|
||||||
|
|
||||||
|
# ========== Helpers ==========
|
||||||
|
have(){ command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
# Iconos locales (sin instalar nada en el sistema/usuario)
|
||||||
|
ensure_local_icons(){
|
||||||
|
local base="$INSTALLER_DIR/icons/hicolor/256x256/apps"
|
||||||
|
local idx="$INSTALLER_DIR/icons/hicolor/index.theme"
|
||||||
|
mkdir -p "$base"
|
||||||
|
install -m 0644 "$ICON_OASIS" "$base/oasis-logo.png"
|
||||||
|
install -m 0644 "$ICON_ECOIN" "$base/ecoin.png"
|
||||||
|
cat > "$idx" <<'EOF'
|
||||||
|
[Icon Theme]
|
||||||
|
Name=Hicolor
|
||||||
|
Comment=Local fallback for Solar Net Hub
|
||||||
|
Directories=256x256/apps
|
||||||
|
|
||||||
|
[256x256/apps]
|
||||||
|
Size=256
|
||||||
|
Context=Applications
|
||||||
|
Type=Fixed
|
||||||
|
EOF
|
||||||
|
# Hacer visible este tema SOLO para este proceso y descendientes
|
||||||
|
export GTK_ICON_THEME_PATH="$INSTALLER_DIR/icons${GTK_ICON_THEME_PATH:+:$GTK_ICON_THEME_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_install(){
|
||||||
|
local pkgs=("$@")
|
||||||
|
if have apt-get; then
|
||||||
|
if have pkexec; then pkexec bash -lc "apt-get update && apt-get install -y ${pkgs[*]}" || sudo apt-get update && sudo apt-get install -y "${pkgs[@]}";
|
||||||
|
else sudo apt-get update && sudo apt-get install -y "${pkgs[@]}"; fi
|
||||||
|
elif have pacman; then
|
||||||
|
if have pkexec; then pkexec bash -lc "pacman -Sy --noconfirm ${pkgs[*]}" || sudo pacman -Sy --noconfirm "${pkgs[@]}";
|
||||||
|
else sudo pacman -Sy --noconfirm "${pkgs[@]}"; fi
|
||||||
|
elif have dnf; then
|
||||||
|
if have pkexec; then pkexec bash -lc "dnf install -y ${pkgs[*]}" || sudo dnf install -y "${pkgs[@]}";
|
||||||
|
else sudo dnf install -y "${pkgs[@]}"; fi
|
||||||
|
elif have zypper; then
|
||||||
|
if have pkexec; then pkexec bash -lc "zypper --non-interactive install ${pkgs[*]}" || sudo zypper --non-interactive install "${pkgs[@]}";
|
||||||
|
else sudo zypper --non-interactive install "${pkgs[@]}"; fi
|
||||||
|
else
|
||||||
|
echo "❌ Gestor de paquetes no soportado." >&2; exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_basics(){
|
||||||
|
local miss=()
|
||||||
|
have yad || miss+=("yad")
|
||||||
|
if ! command -v pkexec >/dev/null 2>&1; then
|
||||||
|
if have apt-get; then miss+=("policykit-1"); else miss+=("polkit"); fi
|
||||||
|
fi
|
||||||
|
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")
|
||||||
|
elif command -v rpm >/dev/null 2>&1; then
|
||||||
|
rpm -q hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme")
|
||||||
|
fi
|
||||||
|
if ((${#miss[@]})); then
|
||||||
|
pkg_install "${miss[@]}"
|
||||||
|
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:"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" NO_AT_BRIDGE=1 yad --name="$NAME" --class="$NAME" "$@"; }
|
||||||
|
|
||||||
|
progress_user(){ # $1 título, $2 comando (string)
|
||||||
|
bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \
|
||||||
|
--width=800 --height=260 --center
|
||||||
|
}
|
||||||
|
progress_root(){ # $1 título, $2 comando (string, root)
|
||||||
|
pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \
|
||||||
|
--width=800 --height=260 --center
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_launcher(){
|
||||||
|
mkdir -p "$APPS_DIR" "$DESKTOP_DIR"
|
||||||
|
local ABS_INSTALLER="$SCRIPT_DIR/installer.sh"
|
||||||
|
local ABS_ICON="$ICON_OASIS"; [ -f "$ABS_ICON" ] || ABS_ICON="$HERO_IMAGE"
|
||||||
|
|
||||||
|
cat > "$APPS_DIR/INSTALLER.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=OASIS Installer
|
||||||
|
Comment=Install or launch OASIS & ECOIN
|
||||||
|
TryExec=/bin/bash
|
||||||
|
Exec=/bin/bash -lc '$ABS_INSTALLER'
|
||||||
|
Icon=$ABS_ICON
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Utility;
|
||||||
|
StartupNotify=true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp -f "$APPS_DIR/INSTALLER.desktop" "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
chmod 644 "$APPS_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
chmod +x "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
command -v update-desktop-database >/dev/null 2>&1 && \
|
||||||
|
update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_user_fonts(){
|
||||||
|
local FONTS_SRC_DIR="$INSTALLER_DIR"
|
||||||
|
local FONTS_DST_DIR="$HOME/.local/share/fonts"
|
||||||
|
local copied=0
|
||||||
|
|
||||||
|
mkdir -p "$FONTS_DST_DIR"
|
||||||
|
|
||||||
|
# Copia Dune Rise (OTF/TTF) si existe en INSTALLER
|
||||||
|
for f in "$FONTS_SRC_DIR"/Dune_Rise.otf "$FONTS_SRC_DIR"/Dune_Rise.ttf; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
cp -f "$f" "$FONTS_DST_DIR/" && copied=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Refresca caché si hemos copiado algo
|
||||||
|
if [ "$copied" = 1 ]; then
|
||||||
|
fc-cache -f >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== 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" ]; }
|
||||||
|
|
||||||
|
# ========== OASIS ==========
|
||||||
|
node_setup_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git curl tar ca-certificates gnupg
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_${1}.x | bash -
|
||||||
|
apt-get install -y nodejs
|
||||||
|
node -v && npm -v
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_install_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ ! -d "$2/.git" ]; then git clone --depth 1 "$1" "$2"; else cd "$2" && git pull --rebase || true; fi
|
||||||
|
cd "$2/src/server"
|
||||||
|
npm install .
|
||||||
|
npm audit fix || true
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_model_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
MODEL_DIR="$1/AI"; mkdir -p "\$MODEL_DIR"
|
||||||
|
if [ ! -f "\$MODEL_DIR/$OASIS_MODEL_FILE" ]; then
|
||||||
|
curl -L -o "\$MODEL_DIR/$OASIS_MODEL_TAR" "$OASIS_MODEL_URL"
|
||||||
|
tar -xzf "\$MODEL_DIR/$OASIS_MODEL_TAR" -C "\$MODEL_DIR"
|
||||||
|
rm "\$MODEL_DIR/$OASIS_MODEL_TAR"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_start_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ -x "$1/oasis.sh" ]; then cd "$1" && exec bash oasis.sh; else cd "$1/src/server" && exec node server.js; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== ECOIN ==========
|
||||||
|
ecoin_deps_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y qt5-qmake qtbase5-dev qttools5-dev-tools \
|
||||||
|
build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev \
|
||||||
|
libleveldb-dev miniupnpc libminiupnpc-dev libqrencode-dev patchelf git curl
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_clone_or_pull_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ ! -d "$2/.git" ]; then git clone "$1" "$2"; else cd "$2" && git pull --rebase || true; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_build_qt_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
cd "$1/ecoin"
|
||||||
|
QMAKE="/usr/lib/x86_64-linux-gnu/qt5/bin/qmake"; [ -x "\$QMAKE" ] || QMAKE="\$(command -v qmake || true)"
|
||||||
|
[ -n "\$QMAKE" ] || { echo "qmake no encontrado"; exit 1; }
|
||||||
|
"\$QMAKE" USE_UPNP=- USE_IPV6=- -o Makefile ecoin-qt.pro
|
||||||
|
qmake USE_UPNP=- USE_IPV6=-
|
||||||
|
make -j"\$(nproc)" || make
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_build_daemon_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
cd "$1/ecoin/src"
|
||||||
|
make -f makefile.linux USE_UPNP=- USE_IPV6=- -j"\$(nproc)" || make -f makefile.linux USE_UPNP=- USE_IPV6=-
|
||||||
|
strip ecoind || true
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_qt_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ -x "$1/ecoin/ecoin-qt" ]; then cd "$1/ecoin" && exec ./ecoin-qt; else echo "ecoin-qt no encontrado"; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ensure_ecoin_conf_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
mkdir -p "$HOME/.ecoin"
|
||||||
|
if [ ! -f "$CONF" ]; then
|
||||||
|
PW="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 24)"
|
||||||
|
cat > "$CONF" <<CFG
|
||||||
|
rpcuser=ecoinrpc
|
||||||
|
rpcpassword=${PW}
|
||||||
|
rpcallowip=127.0.0.1
|
||||||
|
testnet=0
|
||||||
|
noirc=1
|
||||||
|
listen=1
|
||||||
|
server=1
|
||||||
|
daemon=1
|
||||||
|
addnode=46.163.118.220
|
||||||
|
CFG
|
||||||
|
chmod 600 "$CONF"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_create_wallet_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
EOF
|
||||||
|
printf "%s\n" "$(ensure_ecoin_conf_cmd)"
|
||||||
|
cat <<'EOF'
|
||||||
|
if [ -x "$HOME/ecoin/ecoin/ecoin-qt" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin" && ./ecoin-qt || true
|
||||||
|
elif [ -x "$HOME/ecoin/ecoin/src/ecoind" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin/src" && ./ecoind -daemon || true
|
||||||
|
else
|
||||||
|
echo "ECOIN no está compilado. Instálalo primero."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_connect_wallet_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
EOF
|
||||||
|
printf "%s\n" "$(ensure_ecoin_conf_cmd)"
|
||||||
|
cat <<'EOF'
|
||||||
|
if [ -x "$HOME/ecoin/ecoin/src/ecoind" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin/src" && ./ecoind -daemon
|
||||||
|
else
|
||||||
|
echo "ecoind no encontrado. Abre ECOIN (Qt) o recompila."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== Flujos rápidos ==========
|
||||||
|
oasis_quick_install(){
|
||||||
|
local major="${1:-22}" repo="${2:-$OASIS_REPO_DEFAULT}" dir="${3:-$OASIS_DIR_DEFAULT}"
|
||||||
|
progress_root "OASIS: Node $major" "$(node_setup_cmd "$major")"
|
||||||
|
progress_user "OASIS: Repo + npm" "$(oasis_install_cmd "$repo" "$dir")"
|
||||||
|
progress_user "OASIS: Modelo IA" "$(oasis_model_cmd "$dir")"
|
||||||
|
}
|
||||||
|
oasis_quick_start(){ local dir="${1:-$OASIS_DIR_DEFAULT}"; progress_user "OASIS: Arrancar" "$(oasis_start_cmd "$dir")"; }
|
||||||
|
ecoin_quick_install(){
|
||||||
|
local repo="${1:-$ECOIN_REPO_DEFAULT}" dir="${2:-$ECOIN_DIR_DEFAULT}"
|
||||||
|
progress_root "ECOIN: Dependencias" "$(ecoin_deps_cmd)"
|
||||||
|
progress_user "ECOIN: Clonar/Pull" "$(ecoin_clone_or_pull_cmd "$repo" "$dir")"
|
||||||
|
progress_user "ECOIN: Build Qt" "$(ecoin_build_qt_cmd "$dir")"
|
||||||
|
progress_user "ECOIN: Build Daemon" "$(ecoin_build_daemon_cmd "$dir")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Home: 2 columnas x 4 filas (orden fijo, sin vars sueltas) ==========
|
||||||
|
# ========= Home: 2 columnas x 4 filas (orden fijo + lógica básica) =========
|
||||||
|
home_single_dialog(){
|
||||||
|
# Estados rápidos
|
||||||
|
local O_INST=0 E_INST=0 E_WAL=0
|
||||||
|
oasis_installed && O_INST=1
|
||||||
|
ecoin_installed && E_INST=1
|
||||||
|
ecoin_wallet_exists && E_WAL=1
|
||||||
|
|
||||||
|
# Versión OASIS (si existe)
|
||||||
|
local O_VER="—"
|
||||||
|
if [ -f "$OASIS_DIR_DEFAULT/src/server/package.json" ]; then
|
||||||
|
O_VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||||
|
"$OASIS_DIR_DEFAULT/src/server/package.json" | head -n1)"
|
||||||
|
[ -z "$O_VER" ] && O_VER="—"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IZQUIERDA (OASIS)
|
||||||
|
local L1_NAME="OASIS:BTN"
|
||||||
|
local L1_VAL="/bin/bash -lc 'xdg-open https://oasis-project.pub >/dev/null 2>&1 &'"
|
||||||
|
|
||||||
|
local L2_NAME L2_VAL
|
||||||
|
if (( O_INST )); then L2_NAME="INSTALADO:BTN"; L2_VAL="/bin/true"
|
||||||
|
else L2_NAME="INSTALAR:BTN"; L2_VAL="/bin/bash -lc \"$SELF --oasis-install\""; fi
|
||||||
|
|
||||||
|
local L3_NAME="ABRIR:BTN"
|
||||||
|
local L3_VAL; (( O_INST )) && L3_VAL="/bin/bash -lc \"$SELF --oasis-start\"" || L3_VAL="/bin/true"
|
||||||
|
|
||||||
|
local L4_NAME="VERSIÓN ( $O_VER ):BTN"
|
||||||
|
local L4_VAL="/bin/true"
|
||||||
|
|
||||||
|
# DERECHA (ECOIN)
|
||||||
|
local R1_NAME="ECOIN:BTN"
|
||||||
|
local R1_VAL="/bin/bash -lc 'xdg-open https://ecoin.03c8.net/ >/dev/null 2>&1 &'"
|
||||||
|
|
||||||
|
local R2_NAME R2_VAL
|
||||||
|
if (( E_INST )); then R2_NAME="INSTALADO:BTN"; R2_VAL="/bin/true"
|
||||||
|
else R2_NAME="INSTALAR:BTN"; R2_VAL="/bin/bash -lc \"$SELF --ecoin-install\""; fi
|
||||||
|
|
||||||
|
local R3_NAME="CONECTAR CARTERA:BTN"
|
||||||
|
local R3_VAL; (( E_INST )) && R3_VAL="/bin/bash -lc \"$SELF --wallet-connect\"" || R3_VAL="/bin/true"
|
||||||
|
|
||||||
|
local R4_NAME="ABRIR GUI:BTN"
|
||||||
|
local R4_VAL; (( E_INST )) && R4_VAL="/bin/bash -lc \"$SELF --ecoin-gui\"" || R4_VAL="/bin/true"
|
||||||
|
|
||||||
|
# DIÁLOGO (F1 izq, F1 dcha, F2 izq, F2 dcha, ...)
|
||||||
|
local DIALOG_ARGS=(
|
||||||
|
--name="$NAME" --class="$NAME"
|
||||||
|
--title="SOLAR NET HUB" --window-icon="$ICON_OASIS"
|
||||||
|
--center --width=1200 --height=780
|
||||||
|
--image="$HERO_IMAGE" --image-on-top
|
||||||
|
--form --borders=18 --columns=2 --align=center
|
||||||
|
|
||||||
|
# Fila 1
|
||||||
|
--field="$L1_NAME" "$L1_VAL"
|
||||||
|
--field="$R1_NAME" "$R1_VAL"
|
||||||
|
# Fila 2
|
||||||
|
--field="$L2_NAME" "$L2_VAL"
|
||||||
|
--field="$R2_NAME" "$R2_VAL"
|
||||||
|
# Fila 3
|
||||||
|
--field="$L3_NAME" "$L3_VAL"
|
||||||
|
--field="$R3_NAME" "$R3_VAL"
|
||||||
|
# Fila 4
|
||||||
|
--field="$L4_NAME" "$L4_VAL"
|
||||||
|
--field="$R4_NAME" "$R4_VAL"
|
||||||
|
|
||||||
|
--buttons-layout=center
|
||||||
|
--button="Cerrar:0"
|
||||||
|
)
|
||||||
|
|
||||||
|
yad_cmd "${DIALOG_ARGS[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Entrypoint ==========
|
||||||
|
ensure_basics
|
||||||
|
ensure_user_fonts 2>/dev/null || true
|
||||||
|
ensure_local_icons
|
||||||
|
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_single_dialog ;;
|
||||||
|
esac
|
||||||
BIN
INSTALLER_V2/oasis-ecoin.png
Normal file
BIN
INSTALLER_V2/oasis-ecoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
BIN
INSTALLER_V2/oasis-logito.png
Normal file
BIN
INSTALLER_V2/oasis-logito.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
INSTALLER_V2/oasis-logo.png
Normal file
BIN
INSTALLER_V2/oasis-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
746
INSTALLER_V2/panel.py
Normal file
746
INSTALLER_V2/panel.py
Normal file
|
|
@ -0,0 +1,746 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
OASIS Control Panel v2 — Solar Net Hub
|
||||||
|
Panel compacto estilo Mullvad, version mejorada.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk, GLib, Gdk, GdkPixbuf
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
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 = """
|
||||||
|
/* === FUERZA NEGRO EN TODO — sin excepciones === */
|
||||||
|
*,
|
||||||
|
widget, box, grid, stack, overlay,
|
||||||
|
scrolledwindow, viewport, clamp,
|
||||||
|
notebook, frame, paned,
|
||||||
|
label, button, entry, textview,
|
||||||
|
separator, headerbar, actionbar,
|
||||||
|
menubar, menu, menuitem,
|
||||||
|
treeview, iconview, flowbox,
|
||||||
|
progressbar, levelbar, scale,
|
||||||
|
spinbutton, combobox, popover,
|
||||||
|
revealer, expander, listbox, row {
|
||||||
|
background-color: #000000;
|
||||||
|
background: #000000;
|
||||||
|
color: #FF4E00;
|
||||||
|
border-color: #1A1A1A;
|
||||||
|
outline-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
text-shadow: none;
|
||||||
|
font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* El texto dentro de textview tambien */
|
||||||
|
textview text,
|
||||||
|
textview text selection {
|
||||||
|
background-color: #000000;
|
||||||
|
color: #FF4E00;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === HEADER === */
|
||||||
|
box.header {
|
||||||
|
background-color: #080808;
|
||||||
|
border-bottom: 1px solid #1E1E1E;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.header-title {
|
||||||
|
font-size: 13pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #FF4E00;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.header-sub {
|
||||||
|
font-size: 7pt;
|
||||||
|
color: #552200;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === STATUS CARD === */
|
||||||
|
box.status-card {
|
||||||
|
background-color: #080808;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #1A1A1A;
|
||||||
|
}
|
||||||
|
|
||||||
|
box.card-border-running {
|
||||||
|
background-color: #27D980;
|
||||||
|
border-radius: 8px 0px 0px 8px;
|
||||||
|
min-width: 5px;
|
||||||
|
}
|
||||||
|
box.card-border-stopped {
|
||||||
|
background-color: #FF4E00;
|
||||||
|
border-radius: 8px 0px 0px 8px;
|
||||||
|
min-width: 5px;
|
||||||
|
}
|
||||||
|
box.card-border-unknown {
|
||||||
|
background-color: #1A1A1A;
|
||||||
|
border-radius: 8px 0px 0px 8px;
|
||||||
|
min-width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.state-text {
|
||||||
|
font-size: 17pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
label.state-running { color: #27D980; }
|
||||||
|
label.state-stopped { color: #FF4E00; }
|
||||||
|
label.state-unknown { color: #883300; }
|
||||||
|
|
||||||
|
label.state-sub {
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #552200;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dot */
|
||||||
|
label.header-dot { font-size: 8pt; background-color: transparent; }
|
||||||
|
label.dot-running { color: #27D980; }
|
||||||
|
label.dot-stopped { color: #FF4E00; }
|
||||||
|
label.dot-unknown { color: #441100; }
|
||||||
|
|
||||||
|
/* === TABS === */
|
||||||
|
notebook > header,
|
||||||
|
notebook > header tabs,
|
||||||
|
notebook > header tab,
|
||||||
|
tab {
|
||||||
|
background-color: #000000;
|
||||||
|
background: #000000;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab {
|
||||||
|
color: #552200;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
padding: 11px 24px;
|
||||||
|
font-size: 8pt;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
tab:checked {
|
||||||
|
color: #FF4E00;
|
||||||
|
border-bottom: 3px solid #FF4E00;
|
||||||
|
}
|
||||||
|
tab:hover { color: #BB3300; }
|
||||||
|
|
||||||
|
/* === BOTONES === */
|
||||||
|
button, button * {
|
||||||
|
background-color: #000000;
|
||||||
|
background: #000000;
|
||||||
|
color: #FF4E00;
|
||||||
|
box-shadow: none;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.btn {
|
||||||
|
border: 1px solid #FF4E00;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
margin: 5px 5px;
|
||||||
|
font-size: 7.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
min-width: 128px;
|
||||||
|
}
|
||||||
|
button.btn:hover, button.btn:hover * {
|
||||||
|
background-color: #27D980;
|
||||||
|
background: #27D980;
|
||||||
|
color: #000000;
|
||||||
|
border-color: #27D980;
|
||||||
|
}
|
||||||
|
button.btn:active, button.btn:active * {
|
||||||
|
background-color: #1fa865;
|
||||||
|
background: #1fa865;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
button.btn:disabled, button.btn:disabled * {
|
||||||
|
color: #2A0E00;
|
||||||
|
border-color: #1A0A00;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.btn-primary {
|
||||||
|
background-color: #FF4E00;
|
||||||
|
background: #FF4E00;
|
||||||
|
color: #000000;
|
||||||
|
border: 1px solid #FF4E00;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 9px 18px;
|
||||||
|
margin: 5px 5px;
|
||||||
|
font-size: 7.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
min-width: 128px;
|
||||||
|
}
|
||||||
|
button.btn-primary * { background-color: transparent; color: #000000; }
|
||||||
|
button.btn-primary:hover, button.btn-primary:hover * {
|
||||||
|
background-color: #27D980;
|
||||||
|
background: #27D980;
|
||||||
|
color: #000000;
|
||||||
|
border-color: #27D980;
|
||||||
|
}
|
||||||
|
button.btn-primary:disabled, button.btn-primary:disabled * {
|
||||||
|
background-color: #1A0800;
|
||||||
|
background: #1A0800;
|
||||||
|
color: #2A1000;
|
||||||
|
border-color: #1A0800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === INFO === */
|
||||||
|
box.info-box {
|
||||||
|
background-color: #050505;
|
||||||
|
border-top: 1px solid #111111;
|
||||||
|
}
|
||||||
|
label.info-key {
|
||||||
|
color: #662200;
|
||||||
|
font-size: 8pt;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
min-width: 80px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
label.info-val {
|
||||||
|
color: #FF5500;
|
||||||
|
font-size: 8pt;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === LOG === */
|
||||||
|
textview.log-view,
|
||||||
|
textview.log-view text {
|
||||||
|
background-color: #030303;
|
||||||
|
color: #27D980;
|
||||||
|
font-family: "DejaVu Sans Mono", "Monospace", monospace;
|
||||||
|
font-size: 7.5pt;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
scrolledwindow.log-scroll {
|
||||||
|
border: 1px solid #181818;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 6px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
separator {
|
||||||
|
background-color: #0F0F0F;
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.section-lbl {
|
||||||
|
color: #441100;
|
||||||
|
font-size: 7pt;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ── 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 ──────────────────────────────────────────────────────────────────
|
||||||
|
class OasisPanel(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
super().__init__(application=app, title="SOLAR NET HUB")
|
||||||
|
self.set_default_size(390, 620)
|
||||||
|
self.set_resizable(False)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
|
||||||
|
self._build_ui()
|
||||||
|
GLib.timeout_add_seconds(3, self._trigger_poll)
|
||||||
|
self._trigger_poll()
|
||||||
|
|
||||||
|
# ── Layout ────────────────────────────────────────────────────────────
|
||||||
|
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)
|
||||||
|
nb = Gtk.Notebook()
|
||||||
|
nb.set_show_border(False)
|
||||||
|
root.pack_start(nb, True, True, 0)
|
||||||
|
self.notebook = nb
|
||||||
|
nb.append_page(self._make_oasis_tab(), self._tab_lbl("OASIS"))
|
||||||
|
nb.append_page(self._make_ecoin_tab(), self._tab_lbl("ECOIN"))
|
||||||
|
nb.append_page(self._make_sistema_tab(), self._tab_lbl("SISTEMA"))
|
||||||
|
|
||||||
|
def _tab_lbl(self, t):
|
||||||
|
return Gtk.Label(label=t)
|
||||||
|
|
||||||
|
# ── Header ────────────────────────────────────────────────────────────
|
||||||
|
def _make_header(self):
|
||||||
|
outer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
outer.get_style_context().add_class("header")
|
||||||
|
outer.set_margin_top(12)
|
||||||
|
outer.set_margin_bottom(12)
|
||||||
|
outer.set_margin_start(16)
|
||||||
|
outer.set_margin_end(16)
|
||||||
|
|
||||||
|
# Logo
|
||||||
|
logo_path = SCRIPT_DIR / "oasis-logo.png"
|
||||||
|
if logo_path.is_file():
|
||||||
|
try:
|
||||||
|
pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(logo_path), 38, 38, True)
|
||||||
|
outer.pack_start(Gtk.Image.new_from_pixbuf(pb), False, False, 0)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Titulo + subtitulo
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1)
|
||||||
|
vbox.set_margin_start(10)
|
||||||
|
vbox.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
title = Gtk.Label(label="SOLAR NET HUB")
|
||||||
|
title.get_style_context().add_class("header-title")
|
||||||
|
title.set_halign(Gtk.Align.START)
|
||||||
|
vbox.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
sub = Gtk.Label(label="OASIS + ECOIN CONTROL PANEL")
|
||||||
|
sub.get_style_context().add_class("header-sub")
|
||||||
|
sub.set_halign(Gtk.Align.START)
|
||||||
|
vbox.pack_start(sub, False, False, 0)
|
||||||
|
|
||||||
|
outer.pack_start(vbox, True, True, 0)
|
||||||
|
|
||||||
|
# Dot de estado global
|
||||||
|
self.header_dot = Gtk.Label(label="●")
|
||||||
|
self.header_dot.get_style_context().add_class("header-dot")
|
||||||
|
self.header_dot.get_style_context().add_class("dot-unknown")
|
||||||
|
outer.pack_end(self.header_dot, False, False, 0)
|
||||||
|
|
||||||
|
return outer
|
||||||
|
|
||||||
|
# ── OASIS Tab ─────────────────────────────────────────────────────────
|
||||||
|
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 con borde lateral de color
|
||||||
|
card_wrap = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
card_wrap.get_style_context().add_class("status-card")
|
||||||
|
card_wrap.set_margin_top(14)
|
||||||
|
card_wrap.set_margin_bottom(6)
|
||||||
|
card_wrap.set_margin_start(14)
|
||||||
|
card_wrap.set_margin_end(14)
|
||||||
|
|
||||||
|
# Borde lateral (color dinamico)
|
||||||
|
self.oasis_border = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
self.oasis_border.get_style_context().add_class("card-border-unknown")
|
||||||
|
card_wrap.pack_start(self.oasis_border, False, False, 0)
|
||||||
|
|
||||||
|
# Contenido de la card
|
||||||
|
card_inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||||
|
card_inner.set_margin_top(14)
|
||||||
|
card_inner.set_margin_bottom(14)
|
||||||
|
card_inner.set_margin_start(14)
|
||||||
|
card_inner.set_margin_end(14)
|
||||||
|
|
||||||
|
self.oasis_state_lbl = Gtk.Label(label="COMPROBANDO")
|
||||||
|
self.oasis_state_lbl.get_style_context().add_class("state-text")
|
||||||
|
self.oasis_state_lbl.get_style_context().add_class("state-unknown")
|
||||||
|
self.oasis_state_lbl.set_halign(Gtk.Align.START)
|
||||||
|
card_inner.pack_start(self.oasis_state_lbl, False, False, 0)
|
||||||
|
|
||||||
|
self.oasis_sub_lbl = Gtk.Label(label="")
|
||||||
|
self.oasis_sub_lbl.get_style_context().add_class("state-sub")
|
||||||
|
self.oasis_sub_lbl.set_halign(Gtk.Align.START)
|
||||||
|
card_inner.pack_start(self.oasis_sub_lbl, False, False, 0)
|
||||||
|
|
||||||
|
card_wrap.pack_start(card_inner, True, True, 0)
|
||||||
|
box.pack_start(card_wrap, 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(8)
|
||||||
|
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(8)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Info
|
||||||
|
info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||||
|
info_box.get_style_context().add_class("info-box")
|
||||||
|
info_box.set_margin_top(6)
|
||||||
|
grid = Gtk.Grid()
|
||||||
|
grid.set_column_spacing(12)
|
||||||
|
grid.set_row_spacing(7)
|
||||||
|
grid.set_margin_start(18)
|
||||||
|
grid.set_margin_end(18)
|
||||||
|
grid.set_margin_top(12)
|
||||||
|
grid.set_margin_bottom(12)
|
||||||
|
self.oasis_ver_val = self._info_row(grid, 0, "VERSION")
|
||||||
|
self.oasis_node_val = self._info_row(grid, 1, "NODE.JS")
|
||||||
|
self.oasis_dir_val = self._info_row(grid, 2, "RUTA")
|
||||||
|
info_box.pack_start(grid, False, False, 0)
|
||||||
|
box.pack_start(info_box, False, False, 0)
|
||||||
|
|
||||||
|
return scroll
|
||||||
|
|
||||||
|
# ── ECOIN Tab ─────────────────────────────────────────────────────────
|
||||||
|
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_wrap = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
card_wrap.get_style_context().add_class("status-card")
|
||||||
|
for edge in ("top", "bottom", "start", "end"):
|
||||||
|
getattr(card_wrap, f"set_margin_{edge}")(14)
|
||||||
|
card_wrap.set_margin_bottom(6)
|
||||||
|
|
||||||
|
self.ecoin_border = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
self.ecoin_border.get_style_context().add_class("card-border-unknown")
|
||||||
|
card_wrap.pack_start(self.ecoin_border, False, False, 0)
|
||||||
|
|
||||||
|
card_inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||||
|
card_inner.set_margin_top(14)
|
||||||
|
card_inner.set_margin_bottom(14)
|
||||||
|
card_inner.set_margin_start(14)
|
||||||
|
card_inner.set_margin_end(14)
|
||||||
|
|
||||||
|
self.ecoin_state_lbl = Gtk.Label(label="COMPROBANDO")
|
||||||
|
self.ecoin_state_lbl.get_style_context().add_class("state-text")
|
||||||
|
self.ecoin_state_lbl.get_style_context().add_class("state-unknown")
|
||||||
|
self.ecoin_state_lbl.set_halign(Gtk.Align.START)
|
||||||
|
card_inner.pack_start(self.ecoin_state_lbl, False, False, 0)
|
||||||
|
|
||||||
|
self.ecoin_sub_lbl = Gtk.Label(label="")
|
||||||
|
self.ecoin_sub_lbl.get_style_context().add_class("state-sub")
|
||||||
|
self.ecoin_sub_lbl.set_halign(Gtk.Align.START)
|
||||||
|
card_inner.pack_start(self.ecoin_sub_lbl, False, False, 0)
|
||||||
|
|
||||||
|
card_wrap.pack_start(card_inner, True, True, 0)
|
||||||
|
box.pack_start(card_wrap, False, False, 0)
|
||||||
|
|
||||||
|
r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
r1.set_halign(Gtk.Align.CENTER)
|
||||||
|
r1.set_margin_top(8)
|
||||||
|
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)
|
||||||
|
|
||||||
|
r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
|
||||||
|
r2.set_halign(Gtk.Align.CENTER)
|
||||||
|
r2.set_margin_bottom(8)
|
||||||
|
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)
|
||||||
|
|
||||||
|
info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||||
|
info_box.get_style_context().add_class("info-box")
|
||||||
|
info_box.set_margin_top(6)
|
||||||
|
grid = Gtk.Grid()
|
||||||
|
grid.set_column_spacing(12)
|
||||||
|
grid.set_row_spacing(7)
|
||||||
|
grid.set_margin_start(18)
|
||||||
|
grid.set_margin_end(18)
|
||||||
|
grid.set_margin_top(12)
|
||||||
|
grid.set_margin_bottom(12)
|
||||||
|
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")
|
||||||
|
info_box.pack_start(grid, False, False, 0)
|
||||||
|
box.pack_start(info_box, False, False, 0)
|
||||||
|
|
||||||
|
return scroll
|
||||||
|
|
||||||
|
# ── SISTEMA Tab ───────────────────────────────────────────────────────
|
||||||
|
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("section-lbl")
|
||||||
|
hdr.set_halign(Gtk.Align.START)
|
||||||
|
hdr.set_margin_start(18)
|
||||||
|
hdr.set_margin_top(14)
|
||||||
|
hdr.set_margin_bottom(6)
|
||||||
|
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_halign(Gtk.Align.CENTER)
|
||||||
|
btn.set_margin_top(8)
|
||||||
|
btn.set_margin_bottom(12)
|
||||||
|
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
|
||||||
|
|
||||||
|
# ── Card border helper ────────────────────────────────────────────────
|
||||||
|
def _set_border(self, widget, state):
|
||||||
|
for c in ("card-border-running", "card-border-stopped", "card-border-unknown"):
|
||||||
|
widget.get_style_context().remove_class(c)
|
||||||
|
widget.get_style_context().add_class(f"card-border-{state}")
|
||||||
|
|
||||||
|
def _set_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 _set_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
|
||||||
|
h = "running" if o_run else ("stopped" if o_inst else "unknown")
|
||||||
|
self._set_dot(self.header_dot, h)
|
||||||
|
|
||||||
|
# OASIS card
|
||||||
|
o_state = "running" if o_run else ("stopped" if o_inst else "unknown")
|
||||||
|
self._set_border(self.oasis_border, o_state)
|
||||||
|
if o_run:
|
||||||
|
self._set_state(self.oasis_state_lbl, "ACTIVO", "running")
|
||||||
|
self.oasis_sub_lbl.set_text("servidor corriendo 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._set_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._set_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 card
|
||||||
|
e_state = "running" if e_qt else ("stopped" if e_inst else "unknown")
|
||||||
|
self._set_border(self.ecoin_border, e_state)
|
||||||
|
if e_inst:
|
||||||
|
self._set_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._set_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("Si" if e_wall else "No")
|
||||||
|
self.ecoin_qt_val.set_text("Si" if e_qt else "No")
|
||||||
|
self.ecoin_daemon_val.set_text("Si" if e_dmn else "No")
|
||||||
|
|
||||||
|
# ── Acciones ─────────────────────────────────────────────────────────
|
||||||
|
def _run(self, *args):
|
||||||
|
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 (codigo {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
|
||||||
|
|
||||||
|
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 ...")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
def _on_clear_log(self, _):
|
||||||
|
self.log_buf.set_text("")
|
||||||
|
|
||||||
|
|
||||||
|
# ── App ────────────────────────────────────────────────────────────────────
|
||||||
|
class OasisApp(Gtk.Application):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(application_id="net.solarnethub.panel.v2")
|
||||||
|
|
||||||
|
def do_activate(self):
|
||||||
|
win = OasisPanel(self)
|
||||||
|
win.show_all()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Manejo limpio de Ctrl+C — sin traceback
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
|
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()
|
||||||
BIN
INSTALLER_V3/Dune_Rise.otf
Normal file
BIN
INSTALLER_V3/Dune_Rise.otf
Normal file
Binary file not shown.
BIN
INSTALLER_V3/ecoin.png
Normal file
BIN
INSTALLER_V3/ecoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 KiB |
400
INSTALLER_V3/installer.sh
Executable file
400
INSTALLER_V3/installer.sh
Executable file
|
|
@ -0,0 +1,400 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# SOLAR NET HUB - Installer
|
||||||
|
# =========================
|
||||||
|
|
||||||
|
# --- Rutas ---
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
|
: "${SELF:=$SCRIPT_DIR/installer.sh}" # ruta absoluta a este script
|
||||||
|
INSTALLER_DIR="$SCRIPT_DIR"
|
||||||
|
CSS_SOURCE="$INSTALLER_DIR/gtk.css"
|
||||||
|
HERO_IMAGE="$INSTALLER_DIR/oasis-ecoin.png" # imagen con los dos logos (head)
|
||||||
|
|
||||||
|
# ICONOS INDIVIDUALES (se usan en ensure_local_icons y en el .desktop)
|
||||||
|
ICON_OASIS="$INSTALLER_DIR/oasis-logo.png"
|
||||||
|
ICON_ECOIN="$INSTALLER_DIR/ecoin.png"
|
||||||
|
|
||||||
|
APPS_DIR="$HOME/.local/share/applications"
|
||||||
|
DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")"
|
||||||
|
|
||||||
|
# --- Tema local (no toca sistema) ---
|
||||||
|
NAME="SolarHubInstaller"
|
||||||
|
THEME_NAME="SolarHub"
|
||||||
|
YAD_THEME="${THEME_NAME}:dark"
|
||||||
|
ORANGE="#FF4E00"
|
||||||
|
GREEN="#27D980"
|
||||||
|
|
||||||
|
# ========== Helpers ==========
|
||||||
|
have(){ command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
# Iconos locales (sin instalar nada en el sistema/usuario)
|
||||||
|
ensure_local_icons(){
|
||||||
|
local base="$INSTALLER_DIR/icons/hicolor/256x256/apps"
|
||||||
|
local idx="$INSTALLER_DIR/icons/hicolor/index.theme"
|
||||||
|
mkdir -p "$base"
|
||||||
|
install -m 0644 "$ICON_OASIS" "$base/oasis-logo.png"
|
||||||
|
install -m 0644 "$ICON_ECOIN" "$base/ecoin.png"
|
||||||
|
cat > "$idx" <<'EOF'
|
||||||
|
[Icon Theme]
|
||||||
|
Name=Hicolor
|
||||||
|
Comment=Local fallback for Solar Net Hub
|
||||||
|
Directories=256x256/apps
|
||||||
|
|
||||||
|
[256x256/apps]
|
||||||
|
Size=256
|
||||||
|
Context=Applications
|
||||||
|
Type=Fixed
|
||||||
|
EOF
|
||||||
|
# Hacer visible este tema SOLO para este proceso y descendientes
|
||||||
|
export GTK_ICON_THEME_PATH="$INSTALLER_DIR/icons${GTK_ICON_THEME_PATH:+:$GTK_ICON_THEME_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_install(){
|
||||||
|
local pkgs=("$@")
|
||||||
|
if have apt-get; then
|
||||||
|
if have pkexec; then pkexec bash -lc "apt-get update && apt-get install -y ${pkgs[*]}" || sudo apt-get update && sudo apt-get install -y "${pkgs[@]}";
|
||||||
|
else sudo apt-get update && sudo apt-get install -y "${pkgs[@]}"; fi
|
||||||
|
elif have pacman; then
|
||||||
|
if have pkexec; then pkexec bash -lc "pacman -Sy --noconfirm ${pkgs[*]}" || sudo pacman -Sy --noconfirm "${pkgs[@]}";
|
||||||
|
else sudo pacman -Sy --noconfirm "${pkgs[@]}"; fi
|
||||||
|
elif have dnf; then
|
||||||
|
if have pkexec; then pkexec bash -lc "dnf install -y ${pkgs[*]}" || sudo dnf install -y "${pkgs[@]}";
|
||||||
|
else sudo dnf install -y "${pkgs[@]}"; fi
|
||||||
|
elif have zypper; then
|
||||||
|
if have pkexec; then pkexec bash -lc "zypper --non-interactive install ${pkgs[*]}" || sudo zypper --non-interactive install "${pkgs[@]}";
|
||||||
|
else sudo zypper --non-interactive install "${pkgs[@]}"; fi
|
||||||
|
else
|
||||||
|
echo "❌ Gestor de paquetes no soportado." >&2; exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_basics(){
|
||||||
|
local miss=()
|
||||||
|
have yad || miss+=("yad")
|
||||||
|
if ! command -v pkexec >/dev/null 2>&1; then
|
||||||
|
if have apt-get; then miss+=("policykit-1"); else miss+=("polkit"); fi
|
||||||
|
fi
|
||||||
|
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")
|
||||||
|
elif command -v rpm >/dev/null 2>&1; then
|
||||||
|
rpm -q hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme")
|
||||||
|
fi
|
||||||
|
if ((${#miss[@]})); then
|
||||||
|
pkg_install "${miss[@]}"
|
||||||
|
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:"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" NO_AT_BRIDGE=1 yad --name="$NAME" --class="$NAME" "$@"; }
|
||||||
|
|
||||||
|
progress_user(){ # $1 título, $2 comando (string)
|
||||||
|
bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \
|
||||||
|
--width=800 --height=260 --center
|
||||||
|
}
|
||||||
|
progress_root(){ # $1 título, $2 comando (string, root)
|
||||||
|
pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \
|
||||||
|
--width=800 --height=260 --center
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_launcher(){
|
||||||
|
mkdir -p "$APPS_DIR" "$DESKTOP_DIR"
|
||||||
|
local ABS_INSTALLER="$SCRIPT_DIR/installer.sh"
|
||||||
|
local ABS_ICON="$ICON_OASIS"; [ -f "$ABS_ICON" ] || ABS_ICON="$HERO_IMAGE"
|
||||||
|
|
||||||
|
cat > "$APPS_DIR/INSTALLER.desktop" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=OASIS Installer
|
||||||
|
Comment=Install or launch OASIS & ECOIN
|
||||||
|
TryExec=/bin/bash
|
||||||
|
Exec=/bin/bash -lc '$ABS_INSTALLER'
|
||||||
|
Icon=$ABS_ICON
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Utility;
|
||||||
|
StartupNotify=true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cp -f "$APPS_DIR/INSTALLER.desktop" "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
chmod 644 "$APPS_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
chmod +x "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true
|
||||||
|
command -v update-desktop-database >/dev/null 2>&1 && \
|
||||||
|
update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_user_fonts(){
|
||||||
|
local FONTS_SRC_DIR="$INSTALLER_DIR"
|
||||||
|
local FONTS_DST_DIR="$HOME/.local/share/fonts"
|
||||||
|
local copied=0
|
||||||
|
|
||||||
|
mkdir -p "$FONTS_DST_DIR"
|
||||||
|
|
||||||
|
# Copia Dune Rise (OTF/TTF) si existe en INSTALLER
|
||||||
|
for f in "$FONTS_SRC_DIR"/Dune_Rise.otf "$FONTS_SRC_DIR"/Dune_Rise.ttf; do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
cp -f "$f" "$FONTS_DST_DIR/" && copied=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Refresca caché si hemos copiado algo
|
||||||
|
if [ "$copied" = 1 ]; then
|
||||||
|
fc-cache -f >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== 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" ]; }
|
||||||
|
|
||||||
|
# ========== OASIS ==========
|
||||||
|
node_setup_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y git curl tar ca-certificates gnupg
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_${1}.x | bash -
|
||||||
|
apt-get install -y nodejs
|
||||||
|
node -v && npm -v
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_install_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ ! -d "$2/.git" ]; then git clone --depth 1 "$1" "$2"; else cd "$2" && git pull --rebase || true; fi
|
||||||
|
cd "$2/src/server"
|
||||||
|
npm install .
|
||||||
|
npm audit fix || true
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_model_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
MODEL_DIR="$1/AI"; mkdir -p "\$MODEL_DIR"
|
||||||
|
if [ ! -f "\$MODEL_DIR/$OASIS_MODEL_FILE" ]; then
|
||||||
|
curl -L -o "\$MODEL_DIR/$OASIS_MODEL_TAR" "$OASIS_MODEL_URL"
|
||||||
|
tar -xzf "\$MODEL_DIR/$OASIS_MODEL_TAR" -C "\$MODEL_DIR"
|
||||||
|
rm "\$MODEL_DIR/$OASIS_MODEL_TAR"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
oasis_start_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ -x "$1/oasis.sh" ]; then cd "$1" && exec bash oasis.sh; else cd "$1/src/server" && exec node server.js; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== ECOIN ==========
|
||||||
|
ecoin_deps_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y qt5-qmake qtbase5-dev qttools5-dev-tools \
|
||||||
|
build-essential libssl-dev libssl3 libdb5.3-dev libdb5.3++-dev \
|
||||||
|
libleveldb-dev miniupnpc libminiupnpc-dev libqrencode-dev patchelf git curl
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_clone_or_pull_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ ! -d "$2/.git" ]; then git clone "$1" "$2"; else cd "$2" && git pull --rebase || true; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_build_qt_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
cd "$1/ecoin"
|
||||||
|
QMAKE="/usr/lib/x86_64-linux-gnu/qt5/bin/qmake"; [ -x "\$QMAKE" ] || QMAKE="\$(command -v qmake || true)"
|
||||||
|
[ -n "\$QMAKE" ] || { echo "qmake no encontrado"; exit 1; }
|
||||||
|
"\$QMAKE" USE_UPNP=- USE_IPV6=- -o Makefile ecoin-qt.pro
|
||||||
|
qmake USE_UPNP=- USE_IPV6=-
|
||||||
|
make -j"\$(nproc)" || make
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_build_daemon_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
cd "$1/ecoin/src"
|
||||||
|
make -f makefile.linux USE_UPNP=- USE_IPV6=- -j"\$(nproc)" || make -f makefile.linux USE_UPNP=- USE_IPV6=-
|
||||||
|
strip ecoind || true
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_qt_cmd(){ cat <<EOF
|
||||||
|
set -e
|
||||||
|
if [ -x "$1/ecoin/ecoin-qt" ]; then cd "$1/ecoin" && exec ./ecoin-qt; else echo "ecoin-qt no encontrado"; fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ensure_ecoin_conf_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
mkdir -p "$HOME/.ecoin"
|
||||||
|
if [ ! -f "$CONF" ]; then
|
||||||
|
PW="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 24)"
|
||||||
|
cat > "$CONF" <<CFG
|
||||||
|
rpcuser=ecoinrpc
|
||||||
|
rpcpassword=${PW}
|
||||||
|
rpcallowip=127.0.0.1
|
||||||
|
testnet=0
|
||||||
|
noirc=1
|
||||||
|
listen=1
|
||||||
|
server=1
|
||||||
|
daemon=1
|
||||||
|
addnode=46.163.118.220
|
||||||
|
CFG
|
||||||
|
chmod 600 "$CONF"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_create_wallet_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
EOF
|
||||||
|
printf "%s\n" "$(ensure_ecoin_conf_cmd)"
|
||||||
|
cat <<'EOF'
|
||||||
|
if [ -x "$HOME/ecoin/ecoin/ecoin-qt" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin" && ./ecoin-qt || true
|
||||||
|
elif [ -x "$HOME/ecoin/ecoin/src/ecoind" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin/src" && ./ecoind -daemon || true
|
||||||
|
else
|
||||||
|
echo "ECOIN no está compilado. Instálalo primero."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
ecoin_connect_wallet_cmd(){ cat <<'EOF'
|
||||||
|
set -e
|
||||||
|
CONF="$HOME/.ecoin/ecoin.conf"
|
||||||
|
EOF
|
||||||
|
printf "%s\n" "$(ensure_ecoin_conf_cmd)"
|
||||||
|
cat <<'EOF'
|
||||||
|
if [ -x "$HOME/ecoin/ecoin/src/ecoind" ]; then
|
||||||
|
cd "$HOME/ecoin/ecoin/src" && ./ecoind -daemon
|
||||||
|
else
|
||||||
|
echo "ecoind no encontrado. Abre ECOIN (Qt) o recompila."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== Flujos rápidos ==========
|
||||||
|
oasis_quick_install(){
|
||||||
|
local major="${1:-22}" repo="${2:-$OASIS_REPO_DEFAULT}" dir="${3:-$OASIS_DIR_DEFAULT}"
|
||||||
|
progress_root "OASIS: Node $major" "$(node_setup_cmd "$major")"
|
||||||
|
progress_user "OASIS: Repo + npm" "$(oasis_install_cmd "$repo" "$dir")"
|
||||||
|
progress_user "OASIS: Modelo IA" "$(oasis_model_cmd "$dir")"
|
||||||
|
}
|
||||||
|
oasis_quick_start(){ local dir="${1:-$OASIS_DIR_DEFAULT}"; progress_user "OASIS: Arrancar" "$(oasis_start_cmd "$dir")"; }
|
||||||
|
ecoin_quick_install(){
|
||||||
|
local repo="${1:-$ECOIN_REPO_DEFAULT}" dir="${2:-$ECOIN_DIR_DEFAULT}"
|
||||||
|
progress_root "ECOIN: Dependencias" "$(ecoin_deps_cmd)"
|
||||||
|
progress_user "ECOIN: Clonar/Pull" "$(ecoin_clone_or_pull_cmd "$repo" "$dir")"
|
||||||
|
progress_user "ECOIN: Build Qt" "$(ecoin_build_qt_cmd "$dir")"
|
||||||
|
progress_user "ECOIN: Build Daemon" "$(ecoin_build_daemon_cmd "$dir")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Home: 2 columnas x 4 filas (orden fijo, sin vars sueltas) ==========
|
||||||
|
# ========= Home: 2 columnas x 4 filas (orden fijo + lógica básica) =========
|
||||||
|
home_single_dialog(){
|
||||||
|
# Estados rápidos
|
||||||
|
local O_INST=0 E_INST=0 E_WAL=0
|
||||||
|
oasis_installed && O_INST=1
|
||||||
|
ecoin_installed && E_INST=1
|
||||||
|
ecoin_wallet_exists && E_WAL=1
|
||||||
|
|
||||||
|
# Versión OASIS (si existe)
|
||||||
|
local O_VER="—"
|
||||||
|
if [ -f "$OASIS_DIR_DEFAULT/src/server/package.json" ]; then
|
||||||
|
O_VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||||
|
"$OASIS_DIR_DEFAULT/src/server/package.json" | head -n1)"
|
||||||
|
[ -z "$O_VER" ] && O_VER="—"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IZQUIERDA (OASIS)
|
||||||
|
local L1_NAME="OASIS:BTN"
|
||||||
|
local L1_VAL="/bin/bash -lc 'xdg-open https://oasis-project.pub >/dev/null 2>&1 &'"
|
||||||
|
|
||||||
|
local L2_NAME L2_VAL
|
||||||
|
if (( O_INST )); then L2_NAME="INSTALADO:BTN"; L2_VAL="/bin/true"
|
||||||
|
else L2_NAME="INSTALAR:BTN"; L2_VAL="/bin/bash -lc \"$SELF --oasis-install\""; fi
|
||||||
|
|
||||||
|
local L3_NAME="ABRIR:BTN"
|
||||||
|
local L3_VAL; (( O_INST )) && L3_VAL="/bin/bash -lc \"$SELF --oasis-start\"" || L3_VAL="/bin/true"
|
||||||
|
|
||||||
|
local L4_NAME="VERSIÓN ( $O_VER ):BTN"
|
||||||
|
local L4_VAL="/bin/true"
|
||||||
|
|
||||||
|
# DERECHA (ECOIN)
|
||||||
|
local R1_NAME="ECOIN:BTN"
|
||||||
|
local R1_VAL="/bin/bash -lc 'xdg-open https://ecoin.03c8.net/ >/dev/null 2>&1 &'"
|
||||||
|
|
||||||
|
local R2_NAME R2_VAL
|
||||||
|
if (( E_INST )); then R2_NAME="INSTALADO:BTN"; R2_VAL="/bin/true"
|
||||||
|
else R2_NAME="INSTALAR:BTN"; R2_VAL="/bin/bash -lc \"$SELF --ecoin-install\""; fi
|
||||||
|
|
||||||
|
local R3_NAME="CONECTAR CARTERA:BTN"
|
||||||
|
local R3_VAL; (( E_INST )) && R3_VAL="/bin/bash -lc \"$SELF --wallet-connect\"" || R3_VAL="/bin/true"
|
||||||
|
|
||||||
|
local R4_NAME="ABRIR GUI:BTN"
|
||||||
|
local R4_VAL; (( E_INST )) && R4_VAL="/bin/bash -lc \"$SELF --ecoin-gui\"" || R4_VAL="/bin/true"
|
||||||
|
|
||||||
|
# DIÁLOGO (F1 izq, F1 dcha, F2 izq, F2 dcha, ...)
|
||||||
|
local DIALOG_ARGS=(
|
||||||
|
--name="$NAME" --class="$NAME"
|
||||||
|
--title="SOLAR NET HUB" --window-icon="$ICON_OASIS"
|
||||||
|
--center --width=1200 --height=780
|
||||||
|
--image="$HERO_IMAGE" --image-on-top
|
||||||
|
--form --borders=18 --columns=2 --align=center
|
||||||
|
|
||||||
|
# Fila 1
|
||||||
|
--field="$L1_NAME" "$L1_VAL"
|
||||||
|
--field="$R1_NAME" "$R1_VAL"
|
||||||
|
# Fila 2
|
||||||
|
--field="$L2_NAME" "$L2_VAL"
|
||||||
|
--field="$R2_NAME" "$R2_VAL"
|
||||||
|
# Fila 3
|
||||||
|
--field="$L3_NAME" "$L3_VAL"
|
||||||
|
--field="$R3_NAME" "$R3_VAL"
|
||||||
|
# Fila 4
|
||||||
|
--field="$L4_NAME" "$L4_VAL"
|
||||||
|
--field="$R4_NAME" "$R4_VAL"
|
||||||
|
|
||||||
|
--buttons-layout=center
|
||||||
|
--button="Cerrar:0"
|
||||||
|
)
|
||||||
|
|
||||||
|
yad_cmd "${DIALOG_ARGS[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ========== Entrypoint ==========
|
||||||
|
ensure_basics
|
||||||
|
ensure_user_fonts 2>/dev/null || true
|
||||||
|
ensure_local_icons
|
||||||
|
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_single_dialog ;;
|
||||||
|
esac
|
||||||
BIN
INSTALLER_V3/oasis-logo.png
Normal file
BIN
INSTALLER_V3/oasis-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
349
INSTALLER_V3/panel.py
Executable file
349
INSTALLER_V3/panel.py
Executable file
|
|
@ -0,0 +1,349 @@
|
||||||
|
#!/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 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.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", Gtk.main_quit)
|
||||||
|
|
||||||
|
# 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 ───────────────────────────────────────────────────────
|
||||||
|
self.manager = WebKit2.UserContentManager()
|
||||||
|
self.manager.connect("script-message-received::bridge", self._on_message)
|
||||||
|
self.manager.register_script_message_handler("bridge")
|
||||||
|
|
||||||
|
settings = WebKit2.Settings()
|
||||||
|
settings.set_enable_javascript(True)
|
||||||
|
settings.set_enable_developer_extras(True) # activa inspector por si hace falta depurar
|
||||||
|
settings.set_allow_file_access_from_file_urls(True)
|
||||||
|
settings.set_allow_universal_access_from_file_urls(True)
|
||||||
|
settings.set_enable_page_cache(False) # sin caché de página
|
||||||
|
|
||||||
|
self.webview = WebKit2.WebView.new_with_user_content_manager(self.manager)
|
||||||
|
self.webview.set_settings(settings)
|
||||||
|
self.webview.set_background_color(Gdk.RGBA(0, 0, 0, 1))
|
||||||
|
|
||||||
|
self.win.add(self.webview)
|
||||||
|
|
||||||
|
# Cargar HTML directamente por URI (evita caché de recursos)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# ── 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()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"oasis_installed": o_inst,
|
||||||
|
"oasis_running": o_run,
|
||||||
|
"oasis_version": o_ver,
|
||||||
|
"node_version": o_node,
|
||||||
|
"oasis_dir": str(o_dir) if o_inst else "",
|
||||||
|
"ecoin_installed": e_inst,
|
||||||
|
"ecoin_wallet": e_wall,
|
||||||
|
"ecoin_qt": e_qt,
|
||||||
|
"ecoin_daemon": e_dmn,
|
||||||
|
"ecoin_dir": str(e_dir) if e_inst else "",
|
||||||
|
}
|
||||||
|
GLib.idle_add(self._js, f"updateStatus({json.dumps(data)})")
|
||||||
|
|
||||||
|
# ── JS helpers ────────────────────────────────────────────────────────
|
||||||
|
def _js(self, code):
|
||||||
|
# evaluate_javascript (WebKit2 4.1+) o run_javascript (4.0 fallback)
|
||||||
|
if hasattr(self.webview, "evaluate_javascript"):
|
||||||
|
self.webview.evaluate_javascript(code, -1, None, None, None, None, None)
|
||||||
|
else:
|
||||||
|
self.webview.run_javascript(code, None, None, None)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _log(self, text):
|
||||||
|
self._js(f"appendLog({json.dumps(text)})")
|
||||||
|
|
||||||
|
# ── Mensaje desde JS ──────────────────────────────────────────────────
|
||||||
|
def _on_message(self, _manager, result):
|
||||||
|
try:
|
||||||
|
data = json.loads(result.get_value().to_string())
|
||||||
|
action = data.get("action", "")
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
self._dispatch(action)
|
||||||
|
|
||||||
|
def _dispatch(self, action):
|
||||||
|
installer_cmds = {
|
||||||
|
"oasis-install": ["--oasis-install"],
|
||||||
|
"ecoin-install": ["--ecoin-install"],
|
||||||
|
"ecoin-gui": ["--ecoin-gui"],
|
||||||
|
"ecoin-wallet": ["--wallet-create"],
|
||||||
|
"ecoin-connect": ["--wallet-connect"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if action == "oasis-stop":
|
||||||
|
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 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
|
||||||
|
PORT = 3000
|
||||||
|
oasis_dir = find_oasis_dir()
|
||||||
|
oasis_sh = oasis_dir / "oasis.sh"
|
||||||
|
|
||||||
|
if not oasis_sh.is_file():
|
||||||
|
GLib.idle_add(self._log, f"[Error]: no se encontró oasis.sh en {oasis_dir}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# ¿Ya está escuchando?
|
||||||
|
if oasis_running(PORT):
|
||||||
|
GLib.idle_add(self._log, f"OASIS ya está escuchando en el puerto {PORT}")
|
||||||
|
subprocess.Popen(["xdg-open", f"http://localhost:{PORT}"])
|
||||||
|
GLib.idle_add(self._poll)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Lanzar en background igual que el installer original
|
||||||
|
cmd = f'cd "{oasis_dir}" && exec bash oasis.sh'
|
||||||
|
GLib.idle_add(self._log, f"$ nohup bash -lc '{cmd}' &")
|
||||||
|
try:
|
||||||
|
subprocess.Popen(
|
||||||
|
["bash", "-lc", cmd],
|
||||||
|
stdout=open("/tmp/oasis_gui.log", "w"),
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
GLib.idle_add(self._log, f"[Error al lanzar]: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Esperar hasta 30 s a que el puerto esté activo
|
||||||
|
GLib.idle_add(self._log, "Esperando que OASIS levante en el puerto 3000...")
|
||||||
|
for i in range(30):
|
||||||
|
time.sleep(1)
|
||||||
|
if oasis_running(PORT):
|
||||||
|
GLib.idle_add(self._log, f"OASIS levantado en {PORT}. Abriendo navegador...")
|
||||||
|
subprocess.Popen(["xdg-open", f"http://localhost:{PORT}"])
|
||||||
|
GLib.idle_add(self._poll)
|
||||||
|
return
|
||||||
|
if (i + 1) % 5 == 0:
|
||||||
|
GLib.idle_add(self._log, f" ... {i+1}s")
|
||||||
|
|
||||||
|
GLib.idle_add(self._log, "[Error]: OASIS no levantó en 30 s. Revisa /tmp/oasis_gui.log")
|
||||||
|
GLib.idle_add(self._poll)
|
||||||
|
|
||||||
|
def _kill_oasis(self):
|
||||||
|
GLib.idle_add(self._log, "Deteniendo OASIS...")
|
||||||
|
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]: {e}")
|
||||||
|
GLib.idle_add(self._poll)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main ───────────────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
OasisPanel()
|
||||||
|
Gtk.main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
110
INSTALLER_V3/ui/app.js
Normal file
110
INSTALLER_V3/ui/app.js
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
// =============================================================
|
||||||
|
// Solar Net Hub — app.js
|
||||||
|
// Bridge JS ↔ Python y lógica de UI
|
||||||
|
// =============================================================
|
||||||
|
|
||||||
|
// ── Bridge JS → Python ────────────────────────────────────────
|
||||||
|
function send(action) {
|
||||||
|
window.webkit.messageHandlers.bridge.postMessage(
|
||||||
|
JSON.stringify({ action })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tabs ──────────────────────────────────────────────────────
|
||||||
|
function switchTab(name) {
|
||||||
|
document.querySelectorAll('.tab-panel').forEach(el => el.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.tabBtn').forEach(el => el.classList.remove('active'));
|
||||||
|
const panel = document.getElementById('tab-' + name);
|
||||||
|
const btn = document.querySelector('[data-tab="' + name + '"]');
|
||||||
|
if (panel) panel.classList.add('active');
|
||||||
|
if (btn) btn.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actualizar estado (llamado desde Python) ──────────────────
|
||||||
|
function updateStatus(data) {
|
||||||
|
// --- OASIS ---
|
||||||
|
const oCard = document.getElementById('oasisCard');
|
||||||
|
const state = data.oasis_running ? 'running'
|
||||||
|
: data.oasis_installed ? 'stopped'
|
||||||
|
: 'unknown';
|
||||||
|
|
||||||
|
oCard.dataset.state = state;
|
||||||
|
|
||||||
|
if (data.oasis_running) {
|
||||||
|
document.getElementById('oasisState').textContent = 'ACTIVO';
|
||||||
|
document.getElementById('oasisSub').textContent = 'servidor corriendo en puerto 3000';
|
||||||
|
_btn('btnStart', true);
|
||||||
|
_btn('btnStop', false);
|
||||||
|
_btn('btnInstall', true);
|
||||||
|
_btn('btnBrowser', false);
|
||||||
|
} else if (data.oasis_installed) {
|
||||||
|
document.getElementById('oasisState').textContent = 'INSTALADO';
|
||||||
|
document.getElementById('oasisSub').textContent = 'servidor detenido';
|
||||||
|
_btn('btnStart', false);
|
||||||
|
_btn('btnStop', true);
|
||||||
|
_btn('btnInstall', true);
|
||||||
|
_btn('btnBrowser', true);
|
||||||
|
} else {
|
||||||
|
document.getElementById('oasisState').textContent = 'NO INSTALADO';
|
||||||
|
document.getElementById('oasisSub').textContent = 'instala OASIS para comenzar';
|
||||||
|
_btn('btnStart', true);
|
||||||
|
_btn('btnStop', true);
|
||||||
|
_btn('btnInstall', false);
|
||||||
|
_btn('btnBrowser', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('iVer').textContent = data.oasis_version ? 'v' + data.oasis_version : '—';
|
||||||
|
document.getElementById('iNode').textContent = data.node_version || '—';
|
||||||
|
document.getElementById('iDir').textContent = data.oasis_dir || '—';
|
||||||
|
|
||||||
|
// Global dot
|
||||||
|
document.getElementById('globalDot').dataset.state = state;
|
||||||
|
|
||||||
|
// --- ECOIN ---
|
||||||
|
const eCard = document.getElementById('ecoinCard');
|
||||||
|
const estate = data.ecoin_installed ? 'running' : 'unknown';
|
||||||
|
eCard.dataset.state = estate;
|
||||||
|
|
||||||
|
if (data.ecoin_installed) {
|
||||||
|
document.getElementById('ecoinState').textContent = 'COMPILADO';
|
||||||
|
document.getElementById('ecoinSub').textContent = 'wallet ECOIN disponible';
|
||||||
|
_btn('btnEGui', false);
|
||||||
|
_btn('btnEWallet', false);
|
||||||
|
_btn('btnEConnect', false);
|
||||||
|
} else {
|
||||||
|
document.getElementById('ecoinState').textContent = 'NO INSTALADO';
|
||||||
|
document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar';
|
||||||
|
_btn('btnEGui', true);
|
||||||
|
_btn('btnEWallet', true);
|
||||||
|
_btn('btnEConnect', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('eWallet').textContent = data.ecoin_wallet ? 'Si' : 'No';
|
||||||
|
document.getElementById('eQt').textContent = data.ecoin_qt ? 'Si' : 'No';
|
||||||
|
document.getElementById('eDaemon').textContent = data.ecoin_daemon ? 'Si' : 'No';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Log (llamado desde Python) ────────────────────────────────
|
||||||
|
function appendLog(text) {
|
||||||
|
const area = document.getElementById('logArea');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'log-line';
|
||||||
|
|
||||||
|
if (text.startsWith('$')) div.classList.add('cmd');
|
||||||
|
else if (text.startsWith('──')) div.classList.add('end');
|
||||||
|
else if (text.startsWith('[Error')) div.classList.add('error');
|
||||||
|
|
||||||
|
div.textContent = text;
|
||||||
|
area.appendChild(div);
|
||||||
|
area.scrollTop = area.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLog() {
|
||||||
|
document.getElementById('logArea').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helper: deshabilitar/habilitar botón ──────────────────────
|
||||||
|
function _btn(id, disabled) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.disabled = disabled;
|
||||||
|
}
|
||||||
102
INSTALLER_V3/ui/index.html
Normal file
102
INSTALLER_V3/ui/index.html
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OASIS</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- HEADER -->
|
||||||
|
<header>
|
||||||
|
<div class="header-left">
|
||||||
|
<img src="../oasis-logo.png" class="logo" alt="OASIS">
|
||||||
|
<div class="header-titles">
|
||||||
|
<span class="app-title">OASIS</span>
|
||||||
|
<span class="app-sub">SOLAR NET HUB PANEL</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-dot" id="globalDot" data-state="unknown">●</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- TABS -->
|
||||||
|
<nav class="tabbar">
|
||||||
|
<button class="tabBtn active" data-tab="oasis" onclick="switchTab('oasis')">OASIS</button>
|
||||||
|
<button class="tabBtn" data-tab="ecoin" onclick="switchTab('ecoin')">ECOIN</button>
|
||||||
|
<button class="tabBtn" data-tab="sistema" onclick="switchTab('sistema')">SISTEMA</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- CONTENIDO -->
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<!-- ── OASIS ── -->
|
||||||
|
<section class="tab-panel active" id="tab-oasis">
|
||||||
|
|
||||||
|
<div class="status-card" id="oasisCard" data-state="unknown">
|
||||||
|
<div class="card-stripe"></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-state" id="oasisState">COMPROBANDO</div>
|
||||||
|
<div class="card-sub" id="oasisSub">iniciando...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="btn primary" id="btnStart" onclick="send('oasis-start')" disabled>▶ INICIAR</button>
|
||||||
|
<button class="btn" id="btnStop" onclick="send('oasis-stop')" disabled>■ DETENER</button>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="btn" id="btnInstall" onclick="send('oasis-install')">⬇ INSTALAR</button>
|
||||||
|
<button class="btn" id="btnBrowser" onclick="send('oasis-browser')" disabled>◎ ABRIR WEB</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="infobox">
|
||||||
|
<div class="info-row"><span class="ik">VERSION</span><span class="iv" id="iVer">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">NODE.JS</span><span class="iv" id="iNode">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">RUTA</span> <span class="iv" id="iDir">—</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ── ECOIN ── -->
|
||||||
|
<section class="tab-panel" id="tab-ecoin">
|
||||||
|
|
||||||
|
<div class="status-card" id="ecoinCard" data-state="unknown">
|
||||||
|
<div class="card-stripe"></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-state" id="ecoinState">COMPROBANDO</div>
|
||||||
|
<div class="card-sub" id="ecoinSub">iniciando...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="btn primary" id="btnEInstall" onclick="send('ecoin-install')">⬇ INSTALAR</button>
|
||||||
|
<button class="btn" id="btnEGui" onclick="send('ecoin-gui')" disabled>◈ ABRIR GUI</button>
|
||||||
|
</div>
|
||||||
|
<div class="action-row">
|
||||||
|
<button class="btn" id="btnEWallet" onclick="send('ecoin-wallet')" disabled>✦ CREAR WALLET</button>
|
||||||
|
<button class="btn" id="btnEConnect" onclick="send('ecoin-connect')" disabled>⟳ CONECTAR</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="infobox">
|
||||||
|
<div class="info-row"><span class="ik">WALLET</span> <span class="iv" id="eWallet">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">ECOIN-QT</span><span class="iv" id="eQt">—</span></div>
|
||||||
|
<div class="info-row"><span class="ik">ECOIND</span> <span class="iv" id="eDaemon">—</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ── SISTEMA ── -->
|
||||||
|
<section class="tab-panel" id="tab-sistema">
|
||||||
|
<div class="log-header">LOG DE ACTIVIDAD</div>
|
||||||
|
<div class="log-area" id="logArea"></div>
|
||||||
|
<button class="btn btn-clear" onclick="clearLog()">LIMPIAR LOG</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
347
INSTALLER_V3/ui/style.css
Normal file
347
INSTALLER_V3/ui/style.css
Normal file
|
|
@ -0,0 +1,347 @@
|
||||||
|
/* ================================================================
|
||||||
|
OASIS — Panel v3
|
||||||
|
CSS puro, sin limitaciones GTK. Control total como Electron/web.
|
||||||
|
================================================================ */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Dune Rise';
|
||||||
|
src: url('../Dune_Rise.otf') format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #000000;
|
||||||
|
--bg2: #080808;
|
||||||
|
--bg3: #0D0D0D;
|
||||||
|
/* OASIS — naranja */
|
||||||
|
--orange: #FF4E00;
|
||||||
|
--orange-dim: #882200;
|
||||||
|
--orange-mute: #441100;
|
||||||
|
/* ECOIN — amarillo */
|
||||||
|
--yellow: #FFB300;
|
||||||
|
--yellow-dim: #886000;
|
||||||
|
--yellow-mute: #443000;
|
||||||
|
/* LOG — verde neon */
|
||||||
|
--neon: #39FF14;
|
||||||
|
--neon-dim: #1a7a09;
|
||||||
|
/* General */
|
||||||
|
--green: #27D980;
|
||||||
|
--border: #1A1A1A;
|
||||||
|
--font: 'Dune Rise', 'Cantarell', 'Ubuntu', sans-serif;
|
||||||
|
--mono: 'DejaVu Sans Mono', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Reset ── */
|
||||||
|
*, *::before, *::after {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 390px;
|
||||||
|
height: 620px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--orange);
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 10pt;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ─────────────────────────────────────────────────── */
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--bg2);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
object-fit: contain;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-titles {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
font-size: 15pt;
|
||||||
|
color: var(--orange);
|
||||||
|
letter-spacing: 5px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sub {
|
||||||
|
font-size: 6pt;
|
||||||
|
color: var(--orange-mute);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
font-size: 11pt;
|
||||||
|
transition: color 0.4s;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.status-dot[data-state="running"] { color: var(--green); }
|
||||||
|
.status-dot[data-state="stopped"] { color: var(--orange); }
|
||||||
|
.status-dot[data-state="unknown"] { color: var(--orange-mute); }
|
||||||
|
|
||||||
|
/* ── Tab bar ─────────────────────────────────────────────────── */
|
||||||
|
nav.tabbar {
|
||||||
|
display: flex;
|
||||||
|
background: var(--bg);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabBtn {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
padding: 11px 0;
|
||||||
|
font-size: 7.5pt;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-family: var(--font);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab OASIS — naranja */
|
||||||
|
.tabBtn[data-tab="oasis"] { color: var(--orange-dim); }
|
||||||
|
.tabBtn[data-tab="oasis"]:hover { color: var(--orange); }
|
||||||
|
.tabBtn[data-tab="oasis"].active { color: var(--orange); border-bottom-color: var(--orange); }
|
||||||
|
|
||||||
|
/* Tab ECOIN — amarillo */
|
||||||
|
.tabBtn[data-tab="ecoin"] { color: var(--yellow-dim); }
|
||||||
|
.tabBtn[data-tab="ecoin"]:hover { color: var(--yellow); }
|
||||||
|
.tabBtn[data-tab="ecoin"].active { color: var(--yellow); border-bottom-color: var(--yellow); }
|
||||||
|
|
||||||
|
/* Tab SISTEMA — verde neon */
|
||||||
|
.tabBtn[data-tab="sistema"] { color: var(--neon-dim); }
|
||||||
|
.tabBtn[data-tab="sistema"]:hover { color: var(--neon); }
|
||||||
|
.tabBtn[data-tab="sistema"].active { color: var(--neon); border-bottom-color: var(--neon); }
|
||||||
|
|
||||||
|
/* ── Main content ────────────────────────────────────────────── */
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-panel {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
.tab-panel.active { display: flex; }
|
||||||
|
|
||||||
|
/* ── Scrollbar ───────────────────────────────────────────────── */
|
||||||
|
::-webkit-scrollbar { width: 4px; }
|
||||||
|
::-webkit-scrollbar-track { background: var(--bg); }
|
||||||
|
::-webkit-scrollbar-thumb { background: #222; border-radius: 2px; }
|
||||||
|
::-webkit-scrollbar-thumb:hover { background: #333; }
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
OASIS TAB — naranja
|
||||||
|
================================================================ */
|
||||||
|
#tab-oasis .card-state { color: var(--orange-dim); }
|
||||||
|
#tab-oasis .status-card[data-state="running"] .card-state { color: var(--green); }
|
||||||
|
#tab-oasis .status-card[data-state="stopped"] .card-state { color: var(--orange); }
|
||||||
|
#tab-oasis .status-card[data-state="running"] .card-stripe { background: var(--green); }
|
||||||
|
#tab-oasis .status-card[data-state="stopped"] .card-stripe { background: var(--orange); }
|
||||||
|
#tab-oasis .card-sub { color: var(--orange-mute); }
|
||||||
|
#tab-oasis .btn { color: var(--orange); border-color: var(--orange); }
|
||||||
|
#tab-oasis .btn.primary { background: var(--orange); color: #000; border-color: var(--orange); }
|
||||||
|
#tab-oasis .ik { color: var(--orange-dim); }
|
||||||
|
#tab-oasis .iv { color: var(--orange); }
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
ECOIN TAB — amarillo
|
||||||
|
================================================================ */
|
||||||
|
#tab-ecoin .card-state { color: var(--yellow-dim); }
|
||||||
|
#tab-ecoin .status-card[data-state="running"] .card-state { color: var(--yellow); }
|
||||||
|
#tab-ecoin .status-card[data-state="stopped"] .card-state { color: var(--yellow-dim); }
|
||||||
|
#tab-ecoin .status-card[data-state="running"] .card-stripe { background: var(--yellow); }
|
||||||
|
#tab-ecoin .status-card[data-state="stopped"] .card-stripe { background: var(--yellow-dim); }
|
||||||
|
#tab-ecoin .card-sub { color: var(--yellow-mute); }
|
||||||
|
#tab-ecoin .btn { color: var(--yellow); border-color: var(--yellow); }
|
||||||
|
#tab-ecoin .btn.primary { background: var(--yellow); color: #000; border-color: var(--yellow); }
|
||||||
|
#tab-ecoin .ik { color: var(--yellow-dim); }
|
||||||
|
#tab-ecoin .iv { color: var(--yellow); }
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
STATUS CARD — base
|
||||||
|
================================================================ */
|
||||||
|
.status-card {
|
||||||
|
display: flex;
|
||||||
|
margin: 14px 14px 6px;
|
||||||
|
background: var(--bg2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stripe {
|
||||||
|
width: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #1A1A1A;
|
||||||
|
transition: background 0.4s;
|
||||||
|
}
|
||||||
|
.status-card[data-state="unknown"] .card-stripe { background: #1A1A1A; }
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 14px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-state {
|
||||||
|
font-size: 18pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
transition: color 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-sub {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Botones base ────────────────────────────────────────────── */
|
||||||
|
.actions {
|
||||||
|
padding: 6px 14px 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
font-size: 7.5pt;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-family: var(--font);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s, border-color 0.2s, opacity 0.2s;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.btn:hover:not(:disabled) {
|
||||||
|
background: var(--green);
|
||||||
|
color: #000;
|
||||||
|
border-color: var(--green);
|
||||||
|
}
|
||||||
|
.btn:active:not(:disabled) {
|
||||||
|
background: #1fa865;
|
||||||
|
border-color: #1fa865;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.btn-clear {
|
||||||
|
flex: none;
|
||||||
|
width: calc(100% - 28px);
|
||||||
|
margin: 6px 14px 12px;
|
||||||
|
color: var(--neon);
|
||||||
|
border-color: var(--neon);
|
||||||
|
}
|
||||||
|
.btn.btn-clear:hover:not(:disabled) {
|
||||||
|
background: var(--neon);
|
||||||
|
color: #000;
|
||||||
|
border-color: var(--neon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Info box ────────────────────────────────────────────────── */
|
||||||
|
.infobox {
|
||||||
|
margin: 8px 14px 14px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 2px;
|
||||||
|
border-bottom: 1px solid #080808;
|
||||||
|
}
|
||||||
|
.info-row:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
.ik {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
min-width: 82px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.iv {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
SISTEMA / LOG — verde neon
|
||||||
|
================================================================ */
|
||||||
|
.log-header {
|
||||||
|
font-size: 6.5pt;
|
||||||
|
color: var(--neon-dim);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 12px 16px 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-area {
|
||||||
|
flex: 1;
|
||||||
|
background: #030303;
|
||||||
|
border: 1px solid #0a1a0a;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 0 14px 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 7.5pt;
|
||||||
|
color: var(--neon);
|
||||||
|
line-height: 1.55;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line { word-break: break-all; padding: 1px 0; color: var(--neon); }
|
||||||
|
.log-line.cmd { color: var(--orange); }
|
||||||
|
.log-line.end { color: var(--neon-dim); margin-top: 4px; }
|
||||||
|
.log-line.error { color: #ff4444; }
|
||||||
114
start_panel_v2.sh
Executable file
114
start_panel_v2.sh
Executable file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================
|
||||||
|
# SOLAR NET HUB — Panel v2 (lanzador + registro en menú)
|
||||||
|
# =============================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||||
|
PANEL_PY="$REPO_DIR/INSTALLER_V2/panel.py"
|
||||||
|
FONT_SRC="$REPO_DIR/INSTALLER_V2/Dune_Rise.otf"
|
||||||
|
ICON_SRC="$REPO_DIR/INSTALLER_V2/oasis-logo.png"
|
||||||
|
|
||||||
|
APPS_DIR="$HOME/.local/share/applications"
|
||||||
|
ICONS_DIR="$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||||
|
DESKTOP_FILE="$APPS_DIR/solarnethub-panel.desktop"
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
check_display(){
|
||||||
|
if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then
|
||||||
|
echo -e "${C_FAIL}✘ Sin sesión gráfica (DISPLAY/WAYLAND_DISPLAY vacíos).${C_RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_font(){
|
||||||
|
local dst="$HOME/.local/share/fonts/Dune_Rise.otf"
|
||||||
|
if [ -f "$FONT_SRC" ] && [ ! -f "$dst" ]; then
|
||||||
|
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}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
register_desktop(){
|
||||||
|
mkdir -p "$APPS_DIR" "$ICONS_DIR"
|
||||||
|
|
||||||
|
# Copiar icono al directorio de iconos del usuario
|
||||||
|
if [ -f "$ICON_SRC" ]; then
|
||||||
|
cp -f "$ICON_SRC" "$ICONS_DIR/solarnethub.png"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Crear .desktop apuntando a este lanzador
|
||||||
|
cat > "$DESKTOP_FILE" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Solar Net Hub
|
||||||
|
GenericName=OASIS & ECOIN Panel
|
||||||
|
Comment=Panel de control para OASIS y ECOIN
|
||||||
|
Exec=/bin/bash -lc '$REPO_DIR/start_panel_v2.sh'
|
||||||
|
Icon=solarnethub
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;System;Utility;
|
||||||
|
Keywords=oasis;ecoin;solar;hub;crypto;
|
||||||
|
StartupWMClass=solarnethub.panel.v2
|
||||||
|
StartupNotify=true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod 644 "$DESKTOP_FILE"
|
||||||
|
|
||||||
|
# Copiar también al Escritorio si existe
|
||||||
|
local desktop_dir
|
||||||
|
desktop_dir="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")"
|
||||||
|
if [ -d "$desktop_dir" ]; then
|
||||||
|
cp -f "$DESKTOP_FILE" "$desktop_dir/solarnethub-panel.desktop"
|
||||||
|
chmod +x "$desktop_dir/solarnethub-panel.desktop" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Refrescar cache de aplicaciones e iconos
|
||||||
|
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ú: 'Solar Net Hub'${C_RESET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 no soportado. Instala manualmente: python3-gi${C_RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────
|
||||||
|
echo -e "${C_INFO}== SOLAR NET HUB :: Panel v2 ==${C_RESET}"
|
||||||
|
|
||||||
|
check_display
|
||||||
|
install_font 2>/dev/null || true
|
||||||
|
register_desktop 2>/dev/null || true
|
||||||
|
|
||||||
|
if ! check_gi; then
|
||||||
|
install_gi
|
||||||
|
if ! check_gi; then
|
||||||
|
echo -e "${C_FAIL}✘ No se pudo cargar python3-gi.${C_RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${C_OK}✔ Lanzando panel...${C_RESET}"
|
||||||
|
exec python3 "$PANEL_PY" "$@"
|
||||||
124
start_panel_v3.sh
Executable file
124
start_panel_v3.sh
Executable file
|
|
@ -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" <<EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=OASIS
|
||||||
|
GenericName=OASIS Panel
|
||||||
|
Comment=Panel de control OASIS y ECOIN
|
||||||
|
Exec=/bin/bash -lc '$REPO_DIR/start_panel_v3.sh'
|
||||||
|
Icon=oasis
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;System;Utility;
|
||||||
|
Keywords=oasis;ecoin;solar;hub;
|
||||||
|
StartupWMClass=oasis-panel
|
||||||
|
StartupNotify=true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Copiar al Escritorio si existe
|
||||||
|
local desk
|
||||||
|
desk="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")"
|
||||||
|
[ -d "$desk" ] && cp -f "$APPS_DIR/oasis-panel.desktop" "$desk/" && \
|
||||||
|
chmod +x "$desk/oasis-panel.desktop" 2>/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" "$@"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue