Skip to content

Commit 9fabcf3

Browse files
Merge pull request #53 from LayerTwo-Labs/light
Light
2 parents 7812ab1 + a91ec2e commit 9fabcf3

File tree

9 files changed

+233
-5
lines changed

9 files changed

+233
-5
lines changed

.github/workflows/build.yml

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Build and Release
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
tags:
7+
- 'v*'
8+
pull_request:
9+
branches: [ master ]
10+
workflow_dispatch:
11+
12+
jobs:
13+
build:
14+
runs-on: ${{ matrix.os }}
15+
16+
strategy:
17+
matrix:
18+
os: [ubuntu-latest, windows-latest, macos-latest]
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: '18.x'
27+
cache: 'npm'
28+
29+
- name: Install dependencies
30+
run: npm ci
31+
32+
- name: Build React app
33+
env:
34+
CI: false
35+
run: npm run build
36+
37+
- name: Build Electron app
38+
if: matrix.os != 'ubuntu-latest'
39+
run: npm run electron-build
40+
env:
41+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
43+
- name: Build Electron app (Linux)
44+
if: matrix.os == 'ubuntu-latest'
45+
shell: bash
46+
run: |
47+
npm run electron-build
48+
# Remove chrome-sandbox from both unpacked and packaged versions
49+
rm -f dist/linux-unpacked/chrome-sandbox
50+
cd dist
51+
tar xzf drivechain-launcher-*.tar.gz
52+
rm -f drivechain-launcher-*/chrome-sandbox
53+
tar czf drivechain-launcher-*.tar.gz drivechain-launcher-*/
54+
rm -rf drivechain-launcher-*/
55+
env:
56+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57+
58+
- name: Upload Linux artifacts
59+
if: matrix.os == 'ubuntu-latest'
60+
uses: actions/upload-artifact@v4
61+
with:
62+
name: linux-artifacts
63+
path: |
64+
dist/drivechain-launcher-*.tar.gz
65+
66+
- name: Upload Windows artifacts
67+
if: matrix.os == 'windows-latest'
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: windows-artifacts
71+
path: dist/Drivechain-Launcher-Setup-*.exe
72+
73+
- name: Upload macOS artifacts
74+
if: matrix.os == 'macos-latest'
75+
uses: actions/upload-artifact@v4
76+
with:
77+
name: macos-artifacts
78+
path: |
79+
dist/Drivechain-Launcher-*-x64.dmg
80+
dist/Drivechain-Launcher-*-arm64.dmg

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ Drivechain Launcher is an **Electron-based desktop application** that allows use
88
- 🔄 **Start and stop individual chains**
99
- ♻️ **Reset chain data**
1010
- 📊 **View chain details and settings**
11-
- 🌗 **Dark/Light mode toggle**
1211
- 🖥️ **Cross-platform support (Windows, macOS, Linux)**
1312

1413
## 📚 Table of Contents

package.json

+41-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
"name": "drivechain-launcher",
33
"version": "0.1.0",
44
"private": true,
5+
"description": "Drivechain Launcher Application",
6+
"author": {
7+
"name": "Drivechain",
8+
"email": "support@drivechain.info"
9+
},
510
"main": "public/electron.js",
611
"homepage": "./",
712
"dependencies": {
@@ -20,8 +25,6 @@
2025
"bitcoinjs-lib": "^6.1.7",
2126
"concurrently": "^8.2.2",
2227
"cross-env": "^7.0.3",
23-
"electron": "^28.1.0",
24-
"electron-builder": "^24.9.1",
2528
"electron-dl": "^4.0.0",
2629
"electron-is-dev": "^2.0.0",
2730
"electron-store": "^10.0.0",
@@ -74,7 +77,43 @@
7477
"last 1 safari version"
7578
]
7679
},
80+
"build": {
81+
"appId": "com.drivechain.launcher",
82+
"productName": "Drivechain Launcher",
83+
"files": [
84+
"build/**/*",
85+
"public/**/*"
86+
],
87+
"directories": {
88+
"buildResources": "public"
89+
},
90+
"linux": {
91+
"target": ["deb", "tar.gz"],
92+
"category": "Development",
93+
"maintainer": "Drivechain <support@drivechain.info>",
94+
"asarUnpack": ["**/chrome-sandbox"],
95+
"executableName": "drivechain-launcher",
96+
"executableArgs": ["--no-sandbox"],
97+
"artifactName": "Drivechain-Launcher-${version}-${arch}.${ext}"
98+
},
99+
"win": {
100+
"target": "nsis",
101+
"artifactName": "Drivechain-Launcher-Setup-${version}.${ext}"
102+
},
103+
"mac": {
104+
"target": [
105+
{
106+
"target": "dmg",
107+
"arch": ["x64", "arm64"]
108+
}
109+
],
110+
"category": "public.app-category.developer-tools",
111+
"artifactName": "Drivechain-Launcher-${version}-${arch}.${ext}"
112+
}
113+
},
77114
"devDependencies": {
115+
"electron": "^28.1.0",
116+
"electron-builder": "^24.9.1",
78117
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
79118
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
80119
"@babel/preset-env": "^7.26.0",

public/electron.js

+20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
const { app, BrowserWindow, ipcMain, shell } = require("electron");
2+
3+
// Disable sandbox for Linux
4+
if (process.platform === 'linux') {
5+
app.commandLine.appendSwitch('no-sandbox');
6+
app.commandLine.appendSwitch('disable-setuid-sandbox');
7+
process.env.ELECTRON_DISABLE_SANDBOX = '1';
8+
}
29
const path = require("path");
310
const fs = require("fs-extra");
411
const isDev = require("electron-is-dev");
@@ -42,6 +49,7 @@ function createWindow() {
4249
contextIsolation: true,
4350
nodeIntegration: false,
4451
preload: path.join(__dirname, "preload.js"),
52+
sandbox: false
4553
},
4654
});
4755
mainWindow.loadURL(
@@ -199,6 +207,15 @@ function setupIPCHandlers() {
199207
}
200208
});
201209

210+
ipcMain.handle("get-chain-block-count", async (event, chainId) => {
211+
try {
212+
return await chainManager.getChainBlockCount(chainId);
213+
} catch (error) {
214+
console.error("Failed to get chain block count:", error);
215+
return 0;
216+
}
217+
});
218+
202219
ipcMain.handle("reset-chain", async (event, chainId) => {
203220
try {
204221
return await chainManager.resetChain(chainId);
@@ -514,6 +531,9 @@ async function initialize() {
514531
}
515532
}
516533

534+
// Disable sandbox
535+
app.commandLine.appendSwitch('no-sandbox');
536+
517537
app.whenReady().then(initialize);
518538

519539
let isShuttingDown = false;

public/modules/bitcoinMonitor.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class BitcoinMonitor {
1414
};
1515
}
1616

17-
async makeRpcCall(method, params = []) {
17+
async makeRpcCall(method, params = [], quiet_mode = false) {
1818
try {
1919
const response = await axios.post(`http://${this.rpcConfig.host}:${this.rpcConfig.port}`, {
2020
jsonrpc: '1.0',
@@ -29,7 +29,8 @@ class BitcoinMonitor {
2929
});
3030
return response.data.result;
3131
} catch (error) {
32-
console.error(`RPC call failed (${method}):`, error.message);
32+
if (!quiet_mode)
33+
console.error(`RPC call failed (${method}):`, error.message);
3334
throw error;
3435
}
3536
}

public/modules/chainManager.js

+15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ class ChainManager {
1818
this.processCheckers = new Map(); // Track process check intervals
1919
}
2020

21+
async getChainBlockCount(chainId) {
22+
const status = this.chainStatuses.get(chainId);
23+
if (status !== 'running') return -1;
24+
25+
if (chainId === 'bitcoin') {
26+
try {
27+
return await this.bitcoinMonitor.makeRpcCall('getblockcount', [], true);
28+
} catch (error) {
29+
return -1;
30+
}
31+
}
32+
33+
return -1;
34+
}
35+
2136
async isChainReady(chainId) {
2237
const status = this.chainStatuses.get(chainId);
2338
if (status !== 'running') return false;

public/preload.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
1515
getMnemonicPath: (chainId) => ipcRenderer.invoke("get-mnemonic-path", chainId),
1616
stopChain: (chainId) => ipcRenderer.invoke("stop-chain", chainId),
1717
getChainStatus: (chainId) => ipcRenderer.invoke("get-chain-status", chainId),
18+
getChainBlockCount: (chainId) => ipcRenderer.invoke("get-chain-block-count", chainId),
1819
openDataDir: (chainId) => ipcRenderer.invoke("open-data-dir", chainId),
1920
getFullDataDir: (chainId) => ipcRenderer.invoke("get-full-data-dir", chainId),
2021
getWalletDir: (chainId) => ipcRenderer.invoke("get-wallet-dir", chainId),

src/components/Card.js

+43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState, useCallback, useEffect, useRef } from 'react';
22
import { useTheme } from '../contexts/ThemeContext';
33
import ChainSettingsModal from './ChainSettingsModal';
4+
import './StatusLight.css';
45
import ForceStopModal from './ForceStopModal';
56
import SettingsIcon from './SettingsIcon';
67
import Tooltip from './Tooltip';
@@ -23,8 +24,49 @@ const Card = ({
2324
const [lastActionTime, setLastActionTime] = useState(0);
2425
const [tooltipVisible, setTooltipVisible] = useState(false);
2526
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
27+
const [processHealth, setProcessHealth] = useState('offline'); // 'healthy', 'warning', 'error', 'offline'
28+
const [blockCount, setBlockCount] = useState(-1);
2629
const buttonRef = useRef(null);
2730

31+
// Periodic chain status / health check
32+
useEffect(() => {
33+
const fetchBlockCount = async () => {
34+
try {
35+
const count = await window.electronAPI.getChainBlockCount(chain.id);
36+
console.log("new count: ", count)
37+
setBlockCount(count);
38+
} catch (error) {
39+
setBlockCount(-1)
40+
console.error('Failed to fetch block count:', error);
41+
}
42+
};
43+
44+
// Initial fetch
45+
fetchBlockCount();
46+
47+
const interval = setInterval(() => {
48+
fetchBlockCount();
49+
50+
if (chain.status === 'stopping' || chain.status === 'stopped') {
51+
setProcessHealth('offline');
52+
}
53+
else
54+
if (blockCount === -1) {
55+
setProcessHealth('offline');
56+
}
57+
else
58+
if (blockCount === 0) {
59+
setProcessHealth('warning');
60+
}
61+
else
62+
if (blockCount > 0) {
63+
setProcessHealth('healthy');
64+
}
65+
}, 1000);
66+
67+
return () => clearInterval(interval);
68+
}, [chain.id, chain.status, blockCount]);
69+
2870
const checkDependencies = () => {
2971
if (!chain.dependencies || chain.dependencies.length === 0) return true;
3072
return chain.dependencies.every(dep => runningNodes.includes(dep));
@@ -135,6 +177,7 @@ const Card = ({
135177
console.log(`Stopping chain ${chain.id}`);
136178
// Update UI immediately to show stopping state
137179
onUpdateChain(chain.id, { status: 'stopping' });
180+
setProcessHealth('offline');
138181
await onStop(chain.id);
139182
} catch (error) {
140183
console.error('Stop failed:', error);

src/components/StatusLight.css

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.status-light {
2+
width: 12px;
3+
height: 12px;
4+
border-radius: 50%;
5+
position: relative;
6+
display: inline-block;
7+
margin-right: 10px;
8+
transition: background-color 0.3s ease;
9+
}
10+
11+
/* Add z-index to ensure lights are visible */
12+
.status-light.healthy {
13+
background-color: #2ecc71; /* Green */
14+
box-shadow: 0 0 8px rgba(46, 204, 113, 0.5);
15+
}
16+
17+
.status-light.warning {
18+
background-color: #f1c40f; /* Yellow */
19+
box-shadow: 0 0 8px rgba(241, 196, 15, 0.5);
20+
}
21+
22+
.status-light.error {
23+
background-color: #e74c3c; /* Red */
24+
box-shadow: 0 0 8px rgba(231, 76, 60, 0.5);
25+
}
26+
27+
.status-light.offline {
28+
background-color: #95a5a6; /* Gray */
29+
box-shadow: 0 0 8px rgba(149, 165, 166, 0.5);
30+
}

0 commit comments

Comments
 (0)