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
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; }
|
||||
Loading…
Add table
Add a link
Reference in a new issue