diff --git a/DASHBOARD.md b/DASHBOARD.md new file mode 100644 index 0000000..b503994 --- /dev/null +++ b/DASHBOARD.md @@ -0,0 +1,161 @@ +# Web Dashboard Quick Start + +## Starting the Dashboard + +The dashboard is automatically started when you run: + +```bash +docker compose up -d +``` + +This will start three services: +1. **palladium-node** - The Palladium blockchain node +2. **electrumx-server** - The ElectrumX server +3. **palladium-dashboard** - The web monitoring dashboard + +## Accessing the Dashboard + +Once all services are running, access the dashboard at: + +**From the same machine:** +``` +http://localhost:8080 +``` + +**From another device on your network:** +``` +http://:8080 +``` + +To find your Raspberry Pi IP address: +```bash +hostname -I | awk '{print $1}' +``` + +## Dashboard Features + +### Overview Cards +- **System Resources**: Real-time CPU, memory, and disk usage +- **Palladium Node**: Block height, difficulty, connections, sync progress +- **ElectrumX Server**: Active sessions, requests, subscriptions, uptime + +### Charts +- **Block Time History**: Line chart showing block generation times +- **Mempool Transactions**: Bar chart of pending transactions + +### Recent Blocks +Table showing the last 10 blocks with: +- Block height +- Block hash +- Timestamp +- Block size +- Transaction count + +## Auto-Refresh + +The dashboard automatically refreshes every 10 seconds to show the latest data. + +## Troubleshooting + +### Dashboard Not Loading + +1. Check if all containers are running: + ```bash + docker ps + ``` + + You should see three containers: + - `palladium-node` + - `electrumx-server` + - `palladium-dashboard` + +2. Check dashboard logs: + ```bash + docker logs palladium-dashboard + ``` + +3. Check if the port is accessible: + ```bash + curl http://localhost:8080/api/health + ``` + +### Services Showing as "Down" + +If services show as down in the dashboard: + +1. **Palladium Node**: May still be syncing. Check logs: + ```bash + docker logs palladium-node + ``` + +2. **ElectrumX Server**: May be waiting for the node to sync. Check logs: + ```bash + docker logs electrumx-server + ``` + +### Network Access Issues + +If you can't access from another device: + +1. Check firewall rules: + ```bash + sudo ufw status + ``` + +2. Allow port 8080 if needed: + ```bash + sudo ufw allow 8080/tcp + ``` + +3. Verify the dashboard is listening on all interfaces: + ```bash + netstat -tln | grep 8080 + ``` + + Should show: `0.0.0.0:8080` + +## Stopping the Dashboard + +To stop all services including the dashboard: + +```bash +docker compose down +``` + +To stop only the dashboard: + +```bash +docker stop palladium-dashboard +``` + +## Rebuilding the Dashboard + +If you make changes to the dashboard code: + +```bash +docker compose build dashboard +docker compose up -d dashboard +``` + +## API Endpoints + +The dashboard also provides REST API endpoints: + +- `GET /api/health` - Health check +- `GET /api/palladium/info` - Node information +- `GET /api/palladium/blocks/recent` - Recent blocks +- `GET /api/electrumx/stats` - Server statistics +- `GET /api/system/resources` - System resources + +Example: +```bash +curl http://localhost:8080/api/health | jq +``` + +## Security Note + +The dashboard is exposed on `0.0.0.0:8080` making it accessible from your network. If you're running this on a public server, consider: + +1. Using a reverse proxy (nginx) with authentication +2. Restricting access with firewall rules +3. Using HTTPS with SSL certificates diff --git a/Dockerfile.dashboard b/Dockerfile.dashboard new file mode 100644 index 0000000..8b458aa --- /dev/null +++ b/Dockerfile.dashboard @@ -0,0 +1,37 @@ +FROM python:3.11-slim + +LABEL maintainer="Palladium ElectrumX Dashboard" +LABEL description="Web Dashboard for Palladium Node and ElectrumX Server" + +WORKDIR /app + +# Install system dependencies for building Python packages and Docker CLI +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + python3-dev \ + curl \ + ca-certificates \ + && install -m 0755 -d /etc/apt/keyrings \ + && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ + && chmod a+r /etc/apt/keyrings/docker.asc \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends docker-ce-cli \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY web-dashboard/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY web-dashboard/ . + +# Expose port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import requests; requests.get('http://localhost:8080/api/health', timeout=5)" + +# Run the application +CMD ["python", "app.py"] diff --git a/README.md b/README.md index beb9001..553922a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,38 @@ -# ElectrumX with Palladium (PLM) Support +# ElectrumX Server with Palladium (PLM) Full Node -This repository provides a **complete Dockerized setup** of **ElectrumX** with an integrated **Palladium (PLM)** full node. +Complete Dockerized setup for running an **ElectrumX server** with an integrated **Palladium (PLM) full node** and professional **Web Dashboard** for monitoring. Everything runs in Docker containers - no need to install dependencies on your host system! -## What You Get +--- -- **Palladium Full Node** (palladiumd) - Running in Docker -- **ElectrumX Server** - Pre-configured for Palladium -- **Automatic SSL certificates** - Secure connections ready -- **Easy configuration** - Just edit one config file -- **Reuse existing blockchain** - Or sync from scratch -- **Production ready** - Restart policies included +## πŸ“¦ What You Get -## Tested Platforms +- **Palladium Full Node** (palladiumd) - Runs in Docker with full blockchain sync +- **ElectrumX Server** - Pre-configured for Palladium network with automatic indexing +- **Web Dashboard** - Professional monitoring interface with real-time statistics, charts, and peer management +- **Automatic RPC Configuration** - ElectrumX reads credentials directly from palladium.conf +- **Self-Signed SSL Certificates** - Secure connections ready out-of-the-box +- **Production Ready** - Includes restart policies and health checks + +--- + +## πŸ–₯️ Tested Platforms * Debian 12/13 * Ubuntu 24.04/22.04 LTS +* Raspberry Pi OS (ARM64) * WSL2 (Windows Subsystem for Linux) -## Project Structure +**System Requirements:** +- 64-bit system (AMD64 or ARM64) +- 4GB+ RAM recommended +- 50GB+ free disk space for blockchain +- Stable internet connection + +--- + +## πŸ“‹ Project Structure ``` plm-electrumx/ @@ -33,69 +46,74 @@ plm-electrumx/ β”‚ β”œβ”€β”€ blocks/ # Blockchain blocks (auto-generated) β”‚ β”œβ”€β”€ chainstate/ # Blockchain state (auto-generated) β”‚ └── ... # Other runtime data (auto-generated) -β”œβ”€β”€ electrumx-data/ # ElectrumX database (auto-generated) +β”œβ”€β”€ electrumx-data/ # ElectrumX database (auto-generated) +β”œβ”€β”€ web-dashboard/ # Web monitoring dashboard +β”‚ β”œβ”€β”€ app.py # Flask backend API +β”‚ β”œβ”€β”€ templates/ # HTML templates +β”‚ └── static/ # CSS and JavaScript β”œβ”€β”€ Dockerfile.palladium-node # Builds Palladium node container β”œβ”€β”€ Dockerfile.electrumx # Builds ElectrumX server container +β”œβ”€β”€ Dockerfile.dashboard # Builds web dashboard container └── docker-compose.yml # Main orchestration file ``` -**Important:** All blockchain data is stored in `./palladium-node-data/` directory, not externally. - -πŸ”— Palladium Full Node: [palladium-coin/palladiumcore](https://github.com/palladium-coin/palladiumcore) +πŸ”— **Palladium Full Node:** [palladium-coin/palladiumcore](https://github.com/palladium-coin/palladiumcore) --- -## Architecture +## πŸ—οΈ Architecture ``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Electrum β”‚ β”‚ ElectrumX β”‚ β”‚ Palladium β”‚ -β”‚ Clients │◄──►│ Server │◄──►│ Node Daemon β”‚ -β”‚ β”‚ β”‚ (Docker) β”‚ β”‚ (Docker) β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +Internet Router/Firewall Docker Network + β”‚ β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Port β”‚ β”‚ + β”‚ P2P (2333) ────►│ Forward │──────────┐ β”‚ + β”‚ TCP (50001)────►│ Rules │────┐ β”‚ β”‚ + β”‚ SSL (50002)────►│ │──┐ β”‚ β”‚ β”‚ + β”‚ Web (8080) ────►│ │─┐│ β”‚ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚β”‚ β”‚ β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ–Όβ”€β–Όβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ Docker Host β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β”‚ β”‚ β”‚ Palladium Node β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ (palladiumd) β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ Port: 2333 β”‚ β”‚ β”‚ + β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β”‚ β”‚ β”‚ ElectrumX Server β”‚ β”‚ β”‚ +Clients◄──────────────────────── TCP: 50001 β”‚ β”‚ β”‚ +(Electrum β”‚ β”‚ SSL: 50002 β”‚ β”‚ β”‚ + Wallets) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ + β–Ό β”‚ β”‚ Web Dashboard β”‚ β”‚ β”‚ +Browser◄──────────────────────── Port: 8080 β”‚ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ ``` -This setup includes both ElectrumX and Palladium node (palladiumd) running in separate Docker containers. +**Component Communication:** +- **ElectrumX** ↔ **Palladium Node**: RPC over internal Docker network +- **Web Dashboard** ↔ **Palladium Node**: RPC for blockchain data +- **Web Dashboard** ↔ **ElectrumX**: Electrum protocol + Docker API for stats +- **External Clients** β†’ **ElectrumX**: TCP (50001) or SSL (50002) +- **Browsers** β†’ **Web Dashboard**: HTTP (8080) --- -## Requirements +## πŸ”§ Requirements -* [Docker](https://docs.docker.com/get-docker/) -* [Docker Compose](https://docs.docker.com/compose/install/) -* Python 3.10+ (optional, to use `test-server.py`) +* [Docker](https://docs.docker.com/get-docker/) 20.10+ +* [Docker Compose](https://docs.docker.com/compose/install/) 2.0+ * **Palladium binaries** - See installation instructions below -**System Architecture**: This server requires a **64-bit system** (AMD64 or ARM64). 32-bit systems are **not** supported. - --- -## Docker Installation - -If you don't have Docker installed yet, follow the official guide: -- [Install Docker](https://docs.docker.com/get-docker/) - -For Docker Compose: -- [Install Docker Compose](https://docs.docker.com/compose/install/) - ---- - -## Quick Start Guide - -Follow these simple steps to get your ElectrumX server running with Palladium node. - -### Files Overview - -This project uses two separate Dockerfiles: -- **`Dockerfile.palladium-node`**: Builds the Palladium daemon (palladiumd) container -- **`Dockerfile.electrumx`**: Builds the ElectrumX server container - -**Data Storage:** -- **Configuration**: `./palladium-node-data/palladium.conf` (version controlled) -- **Blockchain data**: `./palladium-node-data/` (auto-generated, ignored by git) -- **ElectrumX database**: `./electrumx-data/` (auto-generated, ignored by git) - ---- +## πŸš€ Quick Start Guide ### Step 1: Clone the Repository @@ -108,588 +126,627 @@ cd plm-electrumx ### Step 2: Get Palladium Binaries -**IMPORTANT:** You must download the Palladium binaries that match your system architecture. +**IMPORTANT:** Download binaries matching your system architecture. #### Option A: Download from Official Release -1. Go to the Palladium releases page: [palladium-coin/palladiumcore/releases](https://github.com/palladium-coin/palladiumcore/releases) +1. Go to: [palladium-coin/palladiumcore/releases](https://github.com/palladium-coin/palladiumcore/releases) -2. Download the correct version for your system: - - **Linux x64 (Intel/AMD)**: `palladium-x.x.x-x86_64-linux-gnu.tar.gz` - - **Linux ARM64 (Raspberry Pi, etc.)**: `palladium-x.x.x-aarch64-linux-gnu.tar.gz` +2. Download the correct version: + - **Linux x64**: `palladium-x.x.x-x86_64-linux-gnu.tar.gz` + - **Linux ARM64**: `palladium-x.x.x-aarch64-linux-gnu.tar.gz` -3. Extract the binaries: +3. Extract and copy binaries: ```bash - # Example for Linux x64 tar -xzf palladium-*.tar.gz - ``` - -4. Copy the binaries to the `daemon/` folder: - ```bash mkdir -p daemon cp palladium-*/bin/palladiumd daemon/ cp palladium-*/bin/palladium-cli daemon/ - cp palladium-*/bin/palladium-tx daemon/ - cp palladium-*/bin/palladium-wallet daemon/ - ``` - -5. Make them executable: - ```bash chmod +x daemon/* ``` -#### Option B: Use Existing Palladium Installation - -If you already have Palladium Core installed on your system: - -```bash -mkdir -p daemon -cp /usr/local/bin/palladiumd daemon/ -cp /usr/local/bin/palladium-cli daemon/ -cp /usr/local/bin/palladium-tx daemon/ -cp /usr/local/bin/palladium-wallet daemon/ -``` - #### Verify Installation -Check that the binaries are in place: - ```bash ls -lh daemon/ +# Should show: palladiumd, palladium-cli (both executable) ``` -You should see: -``` --rwxr-xr-x palladiumd # Main daemon (required) --rwxr-xr-x palladium-cli # CLI tool (required) --rwxr-xr-x palladium-tx # Optional --rwxr-xr-x palladium-wallet # Optional -``` - -**Architecture Warning:** The Docker container uses Ubuntu 22.04 base image. Make sure your binaries are compatible with Linux x64 or ARM64. Using macOS or Windows binaries will NOT work. - --- -### Step 3: Configure RPC Credentials +### Step 3: Configure Network and Router -Open the configuration file and change the default credentials: +#### A. Configure RPC Credentials + +Open the configuration file: ```bash nano palladium-node-data/palladium.conf ``` -**IMPORTANT:** Change these two lines: +**Change these credentials:** ```conf -rpcuser=username # ← Change this to your username -rpcpassword=password # ← Change this to a secure password +rpcuser=your_secure_username # ← Change this +rpcpassword=your_secure_password # ← Use a strong password! ``` -Save and close the file (`Ctrl+X`, then `Y`, then `Enter` in nano). +Save and close (`Ctrl+X`, then `Y`, then `Enter`). -**Security Note:** Use a strong password! These credentials control access to your Palladium node. +#### B. Router Port Forwarding (Required for Public Access) + +For your ElectrumX server to be accessible from the internet, you **must** configure port forwarding on your router. + +**Ports to Forward:** + +| Port | Protocol | Service | Description | Required? | +|------|----------|---------|-------------|-----------| +| **2333** | TCP | Palladium P2P | Node connections | **Yes** (for node sync) | +| **50001** | TCP | ElectrumX TCP | Wallet connections | Recommended | +| **50002** | TCP | ElectrumX SSL | Encrypted wallet connections | **Recommended** | +| **8080** | TCP | Web Dashboard | Monitoring interface | Optional | + +**How to Configure Port Forwarding:** + +1. **Find Your Internal IP:** + ```bash + # Linux/Mac: + hostname -I + + # Or check in Docker host: + ip addr show + ``` + Example: `192.168.1.100` + +2. **Access Your Router:** + - Open browser and go to your router's admin page (usually `192.168.1.1` or `192.168.0.1`) + - Login with router credentials + +3. **Add Port Forwarding Rules:** + + Navigate to **Port Forwarding** or **Virtual Server** section and add: + + ``` + Service Name: Palladium P2P + External Port: 2333 + Internal Port: 2333 + Internal IP: 192.168.1.100 (your server's IP) + Protocol: TCP + + Service Name: ElectrumX SSL + External Port: 50002 + Internal Port: 50002 + Internal IP: 192.168.1.100 + Protocol: TCP + + Service Name: ElectrumX TCP + External Port: 50001 + Internal Port: 50001 + Internal IP: 192.168.1.100 + Protocol: TCP + + Service Name: PLM Dashboard (optional) + External Port: 8080 + Internal Port: 8080 + Internal IP: 192.168.1.100 + Protocol: TCP + ``` + +4. **Save and Apply** the configuration. + +5. **Find Your Public IP:** + ```bash + curl ifconfig.me + ``` + Or visit: https://whatismyipaddress.com + +**Security Notes:** +- Only forward port **8080** if you want the dashboard accessible from internet (not recommended without authentication) +- Consider using a VPN for dashboard access instead +- Ports **50001** and **50002** need to be public for Electrum wallets to connect +- Port **2333** is required for the node to sync with the Palladium network --- ### Step 4: (Optional) Copy Existing Blockchain Data -If you already have a synced Palladium blockchain, you can copy it to speed up the initial sync: +If you have a synced Palladium blockchain, copy it to speed up initial sync: ```bash -# Copy from your existing .palladium directory cp -r ~/.palladium/blocks palladium-node-data/ cp -r ~/.palladium/chainstate palladium-node-data/ cp -r ~/.palladium/indexes palladium-node-data/ ``` -**Skip this step** if you want to sync from scratch. The node will automatically start syncing when you run it. +**Skip this** if syncing from scratch - the node will automatically start syncing. --- -### Step 5: Build and Start the Containers - -Now you're ready to start everything: +### Step 5: Build and Start ```bash -docker-compose up -d +docker compose up -d ``` **What happens:** -- Docker builds two images: `palladium-node` and `electrumx-server` -- Starts the Palladium node container first -- Starts the ElectrumX server container (waits for Palladium node) -- Both run in the background (`-d` flag means "detached mode") +1. Builds three Docker images: `palladium-node`, `electrumx-server`, and `palladium-dashboard` +2. Starts Palladium node first +3. Starts ElectrumX (waits for node to be ready) +4. Starts Web Dashboard (connects to both services) -**First time?** The build process can take a few minutes. +**First build takes 5-10 minutes.** --- -### Step 6: Monitor the Logs - -Watch what's happening in real-time: +### Step 6: Monitor Progress ```bash # View all logs -docker-compose logs -f +docker compose logs -f -# View only Palladium node logs -docker-compose logs -f palladiumd - -# View only ElectrumX logs -docker-compose logs -f electrumx +# View specific service +docker compose logs -f palladiumd +docker compose logs -f electrumx +docker compose logs -f dashboard ``` -Press `Ctrl+C` to stop viewing logs (containers keep running). - **What to look for:** -- Palladium node: "UpdateTip" messages (blockchain syncing) -- ElectrumX: "INFO:BlockProcessor:height..." (indexing blocks) +- **Palladium node:** "UpdateTip" messages (syncing blockchain) +- **ElectrumX:** "height X/Y" (indexing blocks) +- **Dashboard:** "Running on http://0.0.0.0:8080" + +Press `Ctrl+C` to exit log view (containers keep running). --- -### Step 7: Verify Everything is Working +### Step 7: Access Web Dashboard -Check if containers are running: +**Local access:** +``` +http://localhost:8080 +``` + +**Network access:** +``` +http://:8080 +``` + +**From internet (if port 8080 forwarded):** +``` +http://:8080 +``` + +The dashboard shows: +- System resources (CPU, RAM, Disk) +- Palladium node status (height, difficulty, connections, sync progress) +- ElectrumX server stats (version, sessions, DB size, uptime, **server IP**, ports) +- Mempool information (transactions, size, usage) +- Recent blocks table +- Network peers (click "Connections" to view detailed peer list) + +--- + +## Web Dashboard Features + +### Main Dashboard (http://localhost:8080) + +**System Monitoring:** +- Real-time CPU, Memory, and Disk usage with animated progress bars +- Health status indicator with pulse animation + +**Palladium Node:** +- Block Height (formatted with thousands separator: 380,050) +- Difficulty (abbreviated: 18.5 M) +- **Connections** (clickable - opens dedicated peers page) +- Network type (MAINNET/TESTNET) +- Sync Progress percentage +- Node version (vX.X.X format) + +**ElectrumX Server:** +- Server Version +- Active Sessions (concurrent connections) +- Database Size +- Uptime +- **Server IP** (for client configuration) +- TCP Port (50001) +- SSL Port (50002) + +**Mempool:** +- Transaction count +- Total size (bytes) +- Usage percentage +- Max size limit + +**Recent Blocks:** +- Last 10 blocks with hash, time, size, and transaction count + +### Network Peers Page (http://localhost:8080/peers) + +**Statistics:** +- Total Peers count +- Inbound connections +- Outbound connections +- Total traffic (sent + received) + +**Detailed Peer List:** +- IP Address and port +- Direction (⬇️ Inbound / ⬆️ Outbound) +- Node version +- Connection time +- Data sent +- Data received +- Total traffic per peer + +**Auto-refresh:** Every 10 seconds + +--- + +## Verify Installation + +### Check Container Status ```bash -docker-compose ps +docker compose ps ``` -You should see both containers with status "Up". +Should show all three containers "Up". -Test the Palladium node: +### Test Palladium Node ```bash -docker exec palladium-node palladium-cli -rpcuser=username -rpcpassword=password getblockchaininfo +docker exec palladium-node palladium-cli \ + -rpcuser=your_username \ + -rpcpassword=your_password \ + getblockchaininfo ``` -(Replace `username` and `password` with your credentials) +### Test ElectrumX Server -You should see blockchain information including the current block height. +```bash +python test-server.py :50002 +``` + +### Check from External Network + +From another machine: +```bash +# Test dashboard +curl http://:8080 + +# Test ElectrumX (with Python) +python test-server.py :50002 +``` --- -## Understanding the Configuration +## Configuration Details -### Palladium Node Configuration File +### Palladium Node Settings -The `palladium-node-data/palladium.conf` file contains all settings for the Palladium daemon: +Key settings in `palladium-node-data/palladium.conf`: -```conf -# RPC credentials (CHANGE THESE!) -rpcuser=username -rpcpassword=password +| Setting | Value | Purpose | +|---------|-------|---------| +| `rpcuser` | `your_username` | RPC authentication | +| `rpcpassword` | `your_password` | RPC authentication | +| `server=1` | Required | Enable RPC server | +| `txindex=1` | Required | Index all transactions (ElectrumX needs this) | +| `addressindex=1` | Recommended | Index addresses for fast queries | +| `timestampindex=1` | Recommended | Index timestamps | +| `spentindex=1` | Recommended | Index spent outputs | +| `rpcbind=0.0.0.0` | Required | Allow Docker connections | +| `rpcallowip=172.17.0.0/16` | Required | Allow Docker network | +| `port=2333` | Default | P2P network port (mainnet) | +| `rpcport=2332` | Default | RPC port (mainnet) | -server=1 -listen=1 -daemon=1 -discover=1 -txindex=1 -addressindex=1 -timestampindex=1 -spentindex=1 +**ZeroMQ Ports (optional):** +- `28332` - Block hash notifications +- `28333` - Transaction hash notifications +- `28334` - Raw block data +- `28335` - Raw transaction data -bind=0.0.0.0 -port=2333 -rpcport=2332 -rpcbind=0.0.0.0 +### ElectrumX Settings -# Allow Docker containers to connect -rpcallowip=172.17.0.0/16 -rpcallowip=172.18.0.0/16 +Configured in `docker-compose.yml`: -maxconnections=50 -fallbackfee=0.0001 - -# Addnodes: -seednode=dnsseed.palladium-coin.store - -addnode=89.117.149.130:2333 -addnode=66.94.115.80:2333 -addnode=173.212.224.67:2333 -addnode=82.165.218.152:2333 - -# ZeroMQ Configuration (optional) -zmqpubrawblock=tcp://0.0.0.0:28334 -zmqpubrawtx=tcp://0.0.0.0:28335 -zmqpubhashblock=tcp://0.0.0.0:28332 -zmqpubhashtx=tcp://0.0.0.0:28333 +```yaml +environment: + COIN: "Palladium" # Always "Palladium" for both networks + NET: "mainnet" # or "testnet" + SERVICES: "tcp://0.0.0.0:50001,ssl://0.0.0.0:50002" + # RPC credentials automatically read from palladium.conf ``` -**Key Settings Explained:** - -| Setting | Purpose | Required? | -|---------|---------|-----------| -| `rpcuser`, `rpcpassword` | Credentials for RPC access | βœ… Yes - CHANGE THESE! | -| `txindex=1` | Index all transactions | βœ… Yes - ElectrumX needs this | -| `addressindex=1` | Index addresses | ⚑ Recommended for performance | -| `timestampindex=1` | Index timestamps | ⚑ Recommended for performance | -| `spentindex=1` | Index spent outputs | ⚑ Recommended for performance | -| `rpcbind=0.0.0.0` | Allow RPC connections from Docker | βœ… Yes | -| `rpcallowip=172.17.0.0/16` | Allow Docker network IPs | βœ… Yes | -| `addnode=...` | Known Palladium nodes to connect | πŸ“‘ Helps with sync | - ---- - -## ElectrumX Configuration - -The ElectrumX container **automatically reads RPC credentials** from the `palladium.conf` file. You don't need to manually configure credentials in `docker-compose.yml` anymore! - -**How it works:** -1. ElectrumX mounts the `palladium-node-data/palladium.conf` file as read-only -2. On startup, it automatically extracts `rpcuser` and `rpcpassword` -3. Builds the `DAEMON_URL` dynamically with the correct credentials - -**Benefits:** -- Single source of truth for credentials (only `palladium.conf`) -- No need to sync credentials between multiple files -- Easier to maintain and more secure - -**Ports:** ElectrumX exposes: -- `50001` β†’ TCP (unencrypted) -- `50002` β†’ SSL (encrypted, recommended) - -**Palladium node ports:** -- `2332` β†’ RPC port (mainnet) -- `2333` β†’ P2P port (mainnet) -- `28332-28335` β†’ ZeroMQ ports (optional) +**Automatic Configuration:** +- ElectrumX reads RPC credentials from mounted `palladium.conf` +- No need to manually configure `DAEMON_URL` +- Single source of truth for credentials --- ## Network Support (Mainnet & Testnet) -This ElectrumX server supports both **Palladium mainnet** and **testnet**. You can switch between networks by modifying the `docker-compose.yml` configuration. - -### Network Comparison - -| Network | COIN Value | NET Value | RPC Port | Bech32 Prefix | Address Prefix | -|---------|-----------|-----------|----------|---------------|----------------| -| **Mainnet** | `Palladium` | `mainnet` | `2332` | `plm` | Standard (starts with `1` or `3`) | -| **Testnet** | `Palladium` | `testnet` | `12332` | `tplm` | Testnet (starts with `t`) | - ---- - ### Running on Mainnet (Default) -The default configuration is set for **mainnet**. No changes are needed if you want to run on mainnet. +Default configuration is for **mainnet** - no changes needed. -**Configuration in `docker-compose.yml`:** -```yaml -environment: - COIN: "Palladium" - NET: "mainnet" - # RPC credentials automatically read from palladium.conf -``` - -**Requirements:** -- Palladium Core node running on **mainnet** -- RPC port: `2332` -- RPC credentials configured in `palladium.conf` - ---- +**Ports:** +- Palladium RPC: `2332` +- Palladium P2P: `2333` +- ElectrumX TCP: `50001` +- ElectrumX SSL: `50002` ### Switching to Testnet -To run ElectrumX on **testnet**, follow these steps: +1. **Edit `palladium.conf`:** + ```conf + testnet=1 + rpcport=12332 + ``` -#### Step 1: Configure Palladium Core for Testnet +2. **Edit `docker-compose.yml`:** + ```yaml + environment: + NET: "testnet" + ``` -Edit your Palladium Core configuration file (`palladium.conf`): +3. **Clear database:** + ```bash + docker compose down + rm -rf ./electrumx-data/* + ``` -```conf -# Enable testnet -testnet=1 +4. **Restart:** + ```bash + docker compose up -d + ``` -# Server mode (required for RPC) -server=1 - -# RPC credentials (change these!) -rpcuser=your_rpc_username -rpcpassword=your_secure_rpc_password - -# RPC port for testnet -rpcport=12332 - -# Allow Docker containers to connect (REQUIRED for ElectrumX) -rpcbind=0.0.0.0 -rpcallowip=127.0.0.1 -rpcallowip=172.16.0.0/12 -``` - -**Important:** The `rpcbind` and `rpcallowip` settings are **required** for Docker connectivity on all platforms. Without these, ElectrumX won't be able to connect to your Palladium node from inside the Docker container. - -Restart your Palladium Core node to apply testnet configuration. - -#### Step 2: Modify docker-compose.yml - -Open `docker-compose.yml` and change these two values in the `environment` section: - -**Before (Mainnet):** -```yaml -environment: - COIN: "Palladium" - NET: "mainnet" - DAEMON_URL: "http://:@host.docker.internal:2332/" -``` - -**After (Testnet):** -```yaml -environment: - COIN: "Palladium" - NET: "testnet" - DAEMON_URL: "http://:@host.docker.internal:12332/" -``` - -**Important changes:** -1. Change `NET` from `"mainnet"` to `"testnet"` -2. Change port in `DAEMON_URL` from `2332` to `12332` -3. Replace `` and `` with your actual testnet RPC credentials - -#### Step 3: Clear Existing Database (Important!) - -When switching networks, you **must** clear the ElectrumX database to avoid conflicts: - -```bash -# Stop the container -docker compose down - -# Remove the database -rm -rf ./electrumx-data/* - -# Or on Windows: -# rmdir /s /q data -# mkdir data -``` - -#### Step 4: Rebuild and Restart - -```bash -# Rebuild and start the container -docker compose up -d --build - -# Monitor the logs -docker compose logs -f -``` - -The ElectrumX server will now sync with the Palladium **testnet** blockchain. - ---- - -### Testnet-Specific Information - -**Genesis Block Hash (Testnet):** -``` -000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943 -``` - -**Address Examples:** -- Legacy (P2PKH): starts with `t` (e.g., `tPLMAddress123...`) -- SegWit (Bech32): starts with `tplm` (e.g., `tplm1q...`) - -**Network Ports:** -- Palladium Core RPC: `12332` -- Palladium Core P2P: `12333` -- ElectrumX TCP: `50001` (same as mainnet) -- ElectrumX SSL: `50002` (same as mainnet) - ---- - -### Switching Back to Mainnet - -To switch back from testnet to mainnet: - -1. Edit `palladium.conf` and remove or comment `testnet=1` -2. Change `rpcport=2332` in `palladium.conf` -3. Restart Palladium Core node -4. In `docker-compose.yml`, change: - - `NET: "testnet"` β†’ `NET: "mainnet"` - - Port in `DAEMON_URL` from `12332` β†’ `2332` -5. Clear database: `rm -rf ./electrumx-data/*` -6. Restart ElectrumX: `docker compose down && docker compose up -d` +**Testnet Ports:** +- Palladium RPC: `12332` +- Palladium P2P: `12333` +- ElectrumX: same (50001, 50002) --- ## Common Commands -### Starting and Stopping +### Container Management ```bash -# Start both containers -docker-compose up -d +# Start all services +docker compose up -d -# Stop both containers -docker-compose down +# Stop all services +docker compose down -# Stop and remove all data (WARNING: deletes ElectrumX database) -docker-compose down -v +# Restart services +docker compose restart -# Restart containers -docker-compose restart +# Restart specific service +docker compose restart electrumx + +# Rebuild after changes +docker compose up -d --build + +# View status +docker compose ps + +# View resource usage +docker stats ``` -### Viewing Logs +### Logs ```bash -# All logs (live feed) -docker-compose logs -f +# All logs (live) +docker compose logs -f -# Only Palladium node -docker-compose logs -f palladiumd - -# Only ElectrumX -docker-compose logs -f electrumx +# Specific service +docker compose logs -f palladiumd +docker compose logs -f electrumx +docker compose logs -f dashboard # Last 100 lines -docker-compose logs --tail=100 +docker compose logs --tail=100 + +# Since specific time +docker compose logs --since 30m ``` -### Checking Status +### Palladium Node Commands ```bash -# Container status -docker-compose ps +# Blockchain info +docker exec palladium-node palladium-cli \ + -rpcuser=USER -rpcpassword=PASS \ + getblockchaininfo -# Palladium blockchain info -docker exec palladium-node palladium-cli -rpcuser=YOUR_USER -rpcpassword=YOUR_PASS getblockchaininfo +# Network info +docker exec palladium-node palladium-cli \ + -rpcuser=USER -rpcpassword=PASS \ + getnetworkinfo -# Palladium network info -docker exec palladium-node palladium-cli -rpcuser=YOUR_USER -rpcpassword=YOUR_PASS getnetworkinfo +# Peer count +docker exec palladium-node palladium-cli \ + -rpcuser=USER -rpcpassword=PASS \ + getconnectioncount -# Check how many peers connected -docker exec palladium-node palladium-cli -rpcuser=YOUR_USER -rpcpassword=YOUR_PASS getpeerinfo | grep addr -``` - -### Rebuilding After Changes - -If you modify configuration or update binaries: - -```bash -# Rebuild images -docker-compose build - -# Rebuild and restart -docker-compose up -d --build - -# Force rebuild (no cache) -docker-compose build --no-cache +# Peer details +docker exec palladium-node palladium-cli \ + -rpcuser=USER -rpcpassword=PASS \ + getpeerinfo ``` --- ## Troubleshooting -### Palladium Node Not Starting +### Container Won't Start -**Problem:** Container exits immediately +**Check logs:** +```bash +docker compose logs +``` -**Solution:** -1. Check logs: `docker-compose logs palladiumd` -2. Verify credentials in `palladium-node-data/palladium.conf` -3. Check if blockchain directory has permissions issues: - ```bash - ls -la palladium-node-data/ - ``` -4. Verify binaries are correct architecture: - ```bash - file daemon/palladiumd - # Should show: ELF 64-bit LSB executable, x86-64 (or aarch64) - ``` +**Common issues:** +- Missing or incorrect RPC credentials +- Port already in use +- Insufficient disk space +- Wrong architecture binaries -### ElectrumX Can't Connect to Palladium Node +### ElectrumX Can't Connect to Node -**Problem:** ElectrumX logs show "connection refused" +**Verify:** +1. Node is running: `docker compose ps` +2. RPC works: `docker exec palladium-node palladium-cli -rpcuser=USER -rpcpassword=PASS getblockchaininfo` +3. Credentials match in `palladium.conf` -**Solution:** -1. Verify credentials match in both files: - - `palladium-node-data/palladium.conf` - - `docker-compose.yml` (DAEMON_URL line) -2. Check if Palladium node is running: - ```bash - docker-compose ps - ``` -3. Test RPC connection: - ```bash - docker exec palladium-node palladium-cli -rpcuser=YOUR_USER -rpcpassword=YOUR_PASS getblockchaininfo - ``` - -### Blockchain Sync is Slow - -**Problem:** Sync takes too long +### Slow Blockchain Sync **Tips:** -- Copy existing blockchain data to `palladium-node-data/` (see Step 5) -- Check internet connection -- Make sure addnodes are configured in `palladium.conf` -- Be patient - initial sync can take hours/days depending on blockchain size +- Copy existing blockchain (Step 4) +- Check internet speed +- Verify addnodes in config +- Be patient (initial sync takes hours/days) ### Port Already in Use -**Problem:** Error "port is already allocated" +```bash +# Check what's using port +sudo lsof -i :50001 -**Solution:** -1. Check what's using the port: - ```bash - sudo lsof -i :2332 # or :50001, :50002 - ``` -2. Stop the conflicting service, or change ports in `docker-compose.yml` +# Kill process or change port in docker-compose.yml +``` -### Disk Space Running Out +### Dashboard Shows Wrong Data -**Problem:** Blockchain takes too much space +**Force refresh:** +1. Clear browser cache (Ctrl+Shift+R) +2. Check dashboard logs: `docker compose logs dashboard` +3. Restart dashboard: `docker compose restart dashboard` -**Solution:** -- The Palladium blockchain can be several GB -- Make sure you have enough space in the project directory -- Check disk usage: - ```bash - df -h - du -sh palladium-node-data/ - du -sh data/ - ``` +### Binary Architecture Error -### Binary Architecture Mismatch +```bash +# Check your system +uname -m +# x86_64 = Intel/AMD 64-bit +# aarch64 = ARM 64-bit -**Problem:** "cannot execute binary file: Exec format error" +# Check binary +file daemon/palladiumd +# Should match system architecture -**Solution:** -- You're using binaries for the wrong architecture -- Check your system: `uname -m` - - `x86_64` β†’ need x86_64 Linux binaries - - `aarch64` β†’ need ARM64 Linux binaries -- Download the correct binaries and replace them in `daemon/` -- Rebuild: `docker-compose build --no-cache` +# Fix: download correct binaries and rebuild +docker compose build --no-cache +``` --- -## Testing with `test-server.py` +## Security Recommendations -The `test-server.py` script allows you to connect to the ElectrumX server and test its APIs. +### Production Deployment -Usage example: +1. **Strong Credentials:** + - Use long, random passwords for RPC + - Don't use default credentials -```bash -python test-server.py 127.0.0.1:50002 +2. **Firewall Configuration:** + ```bash + # Allow only required ports + sudo ufw allow 2333/tcp # P2P + sudo ufw allow 50001/tcp # ElectrumX TCP + sudo ufw allow 50002/tcp # ElectrumX SSL + # Don't expose 8080 publicly without authentication + sudo ufw enable + ``` + +3. **SSL Certificates:** + - Default uses self-signed certificates + - For production, use valid SSL certificates (Let's Encrypt) + +4. **Dashboard Access:** + - Consider adding authentication + - Use VPN for remote access + - Or restrict to local network only + +5. **Regular Updates:** + ```bash + # Update Palladium binaries + # Update Docker images + docker compose pull + docker compose up -d + ``` + +6. **Monitoring:** + - Set up log monitoring + - Monitor disk space + - Watch for unusual activity in dashboard + +--- + +## Performance Tuning + +### System Resources + +**Minimum:** +- 2 CPU cores +- 4GB RAM +- 50GB storage + +**Recommended:** +- 4+ CPU cores +- 8GB+ RAM +- 100GB+ SSD storage + +### Docker Resource Limits + +Edit `docker-compose.yml` to add limits: + +```yaml +services: + palladiumd: + deploy: + resources: + limits: + cpus: '2' + memory: 2G ``` -The script will perform: +### ElectrumX Performance -* Handshake (`server.version`) -* Feature request (`server.features`) -* Block header subscription (`blockchain.headers.subscribe`) +Increase concurrent indexing (in `docker-compose.yml`): + +```yaml +environment: + INITIAL_CONCURRENT: "4" # Default: 2 +``` --- ## Notes -* `coins_plm.py` defines both **Palladium (PLM)** mainnet and **PalladiumTestnet** classes -* See "Network Support" section for switching between mainnet and testnet -* Production recommendations: - - * Protect RPC credentials - * Use valid SSL certificates - * Monitor containers (logs, metrics, alerts) +* **Data Persistence:** All data stored in `./palladium-node-data/` and `./electrumx-data/` +* **Backup:** Regularly backup `palladium-node-data/wallet.dat` if you store funds +* **Network Switch:** Always clear ElectrumX database when switching networks +* **Updates:** Check for Palladium Core updates regularly --- ## License -Distributed under the **MIT** license. See the `LICENSE` file for details. +Distributed under the **MIT** license. See `LICENSE` file for details. + +--- + +## πŸ†˜ Support + +- **Issues:** [GitHub Issues](https://github.com/palladium-coin/plm-electrumx/issues) +- **Palladium Community:** [Palladium Coin](https://github.com/palladium-coin) +- **ElectrumX Documentation:** [Official Docs](https://electrumx.readthedocs.io/) + +--- + +## Credits + +- **ElectrumX:** [kyuupichan/electrumx](https://github.com/kyuupichan/electrumx) +- **Palladium Core:** [palladium-coin/palladiumcore](https://github.com/palladium-coin/palladiumcore) diff --git a/docker-compose.yml b/docker-compose.yml index f28f049..51283e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,4 +70,27 @@ services: volumes: - ./electrumx-data:/data - - ./palladium-node-data/palladium.conf:/palladium-config/palladium.conf:ro \ No newline at end of file + - ./palladium-node-data/palladium.conf:/palladium-config/palladium.conf:ro + + dashboard: + build: + context: . + dockerfile: Dockerfile.dashboard + image: palladium-dashboard:local + container_name: palladium-dashboard + restart: unless-stopped + depends_on: + - palladiumd + - electrumx + ports: + - "0.0.0.0:8080:8080" # Web Dashboard (accessible from network) + + environment: + PALLADIUM_RPC_HOST: "palladiumd" + PALLADIUM_RPC_PORT: "2332" + ELECTRUMX_RPC_HOST: "electrumx" + ELECTRUMX_RPC_PORT: "8000" + + volumes: + - ./palladium-node-data/palladium.conf:/palladium-config/palladium.conf:ro + - /var/run/docker.sock:/var/run/docker.sock:ro \ No newline at end of file diff --git a/web-dashboard/README.md b/web-dashboard/README.md new file mode 100644 index 0000000..c0c70f4 --- /dev/null +++ b/web-dashboard/README.md @@ -0,0 +1,43 @@ +# Palladium Network Dashboard + +Modern web dashboard for monitoring Palladium Node and ElectrumX Server statistics. + +## Features + +- **Real-time Monitoring**: Auto-refresh every 10 seconds +- **Palladium Node Stats**: Block height, difficulty, connections, sync progress +- **ElectrumX Server Stats**: Active sessions, requests, subscriptions, uptime +- **System Resources**: CPU, memory, and disk usage monitoring +- **Interactive Charts**: Block time history and mempool transaction graphs +- **Recent Blocks Table**: View the latest blocks with detailed information +- **Professional UI**: Dark theme with responsive design + +## API Endpoints + +- `GET /` - Main dashboard page +- `GET /api/health` - Health check for all services +- `GET /api/palladium/info` - Palladium node information +- `GET /api/palladium/blocks/recent` - Recent blocks +- `GET /api/electrumx/stats` - ElectrumX server statistics +- `GET /api/system/resources` - System resource usage + +## Technology Stack + +- **Backend**: Python Flask +- **Frontend**: HTML5, CSS3, JavaScript +- **Charts**: Chart.js +- **Design**: Modern dark theme with gradient accents + +## Access + +After starting the services with `docker-compose up -d`, access the dashboard at: + +``` +http://localhost:8080 +``` + +Or from another device on the network: + +``` +http://:8080 +``` diff --git a/web-dashboard/app.py b/web-dashboard/app.py new file mode 100644 index 0000000..9b3d5a3 --- /dev/null +++ b/web-dashboard/app.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +""" +Web Dashboard API for Palladium Node and ElectrumX Server Statistics +""" + +from flask import Flask, jsonify, render_template, request +from flask_cors import CORS +import requests +import json +import os +import time +from datetime import datetime +import psutil +import socket + +app = Flask(__name__) +CORS(app) + +# Configuration +PALLADIUM_RPC_HOST = os.getenv('PALLADIUM_RPC_HOST', 'palladiumd') +PALLADIUM_RPC_PORT = int(os.getenv('PALLADIUM_RPC_PORT', '2332')) +ELECTRUMX_RPC_HOST = os.getenv('ELECTRUMX_RPC_HOST', 'electrumx') +ELECTRUMX_RPC_PORT = int(os.getenv('ELECTRUMX_RPC_PORT', '8000')) + +# Read RPC credentials from palladium.conf +def get_rpc_credentials(): + """Read RPC credentials from palladium.conf""" + try: + conf_path = '/palladium-config/palladium.conf' + rpc_user = None + rpc_password = None + + with open(conf_path, 'r') as f: + for line in f: + line = line.strip() + if line.startswith('rpcuser='): + rpc_user = line.split('=', 1)[1] + elif line.startswith('rpcpassword='): + rpc_password = line.split('=', 1)[1] + + return rpc_user, rpc_password + except Exception as e: + print(f"Error reading RPC credentials: {e}") + return None, None + +def palladium_rpc_call(method, params=None): + """Make RPC call to Palladium node""" + if params is None: + params = [] + + rpc_user, rpc_password = get_rpc_credentials() + if not rpc_user or not rpc_password: + return None + + url = f"http://{PALLADIUM_RPC_HOST}:{PALLADIUM_RPC_PORT}" + headers = {'content-type': 'application/json'} + payload = { + "jsonrpc": "2.0", + "id": "dashboard", + "method": method, + "params": params + } + + try: + response = requests.post( + url, + auth=(rpc_user, rpc_password), + data=json.dumps(payload), + headers=headers, + timeout=10 + ) + + if response.status_code == 200: + result = response.json() + return result.get('result') + return None + except Exception as e: + print(f"RPC call error ({method}): {e}") + return None + +def get_electrumx_stats(): + """Get ElectrumX statistics via Electrum protocol and system info""" + try: + import socket + import json + import subprocess + from datetime import datetime + + stats = { + 'server_version': 'Unknown', + 'protocol_min': '', + 'protocol_max': '', + 'genesis_hash': '', + 'hash_function': '', + 'pruning': None, + 'sessions': 0, + 'requests': 0, + 'subs': 0, + 'uptime': 0, + 'db_size': 0, + 'tcp_port': 50001, + 'ssl_port': 50002, + 'server_ip': 'Unknown' + } + + # Get server IP address + try: + # Try to get public IP from external service + response = requests.get('https://api.ipify.org?format=json', timeout=3) + if response.status_code == 200: + stats['server_ip'] = response.json().get('ip', 'Unknown') + else: + # Fallback to local IP + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + stats['server_ip'] = s.getsockname()[0] + s.close() + except Exception as e: + print(f"IP detection error: {e}") + # Last fallback: get hostname IP + try: + stats['server_ip'] = socket.gethostbyname(socket.gethostname()) + except: + stats['server_ip'] = 'Unknown' + + # Get server features via Electrum protocol + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + sock.connect((ELECTRUMX_RPC_HOST, 50001)) + + request = { + "jsonrpc": "2.0", + "id": 1, + "method": "server.features", + "params": [] + } + + sock.send((json.dumps(request) + '\n').encode()) + response = sock.recv(4096).decode() + sock.close() + + data = json.loads(response) + if 'result' in data: + result = data['result'] + stats['server_version'] = result.get('server_version', 'Unknown') + stats['protocol_min'] = result.get('protocol_min', '') + stats['protocol_max'] = result.get('protocol_max', '') + stats['genesis_hash'] = result.get('genesis_hash', '')[:16] + '...' + stats['hash_function'] = result.get('hash_function', '') + stats['pruning'] = result.get('pruning') + except Exception as e: + print(f"ElectrumX protocol error: {e}") + + # Try to get container stats via Docker + try: + # Get container uptime + result = subprocess.run( + ['docker', 'inspect', 'electrumx-server', '--format', '{{.State.StartedAt}}'], + capture_output=True, + text=True, + timeout=2 + ) + if result.returncode == 0: + started_at = result.stdout.strip() + # Parse and calculate uptime + from dateutil import parser + start_time = parser.parse(started_at) + uptime_seconds = int((datetime.now(start_time.tzinfo) - start_time).total_seconds()) + stats['uptime'] = uptime_seconds + except Exception as e: + print(f"Docker uptime error: {e}") + + # Try to estimate DB size from data directory + try: + result = subprocess.run( + ['docker', 'exec', 'electrumx-server', 'du', '-sb', '/data'], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0: + db_size = int(result.stdout.split()[0]) + stats['db_size'] = db_size + except Exception as e: + print(f"DB size error: {e}") + + # Count active connections (TCP sessions) + try: + result = subprocess.run( + ['docker', 'exec', 'electrumx-server', 'sh', '-c', + 'netstat -an 2>/dev/null | grep ":50001.*ESTABLISHED" | wc -l'], + capture_output=True, + text=True, + timeout=2 + ) + if result.returncode == 0: + sessions = int(result.stdout.strip()) + stats['sessions'] = sessions + except Exception as e: + print(f"Sessions count error: {e}") + + return stats + except Exception as e: + print(f"ElectrumX stats error: {e}") + return None + +@app.route('/') +def index(): + """Serve main dashboard page""" + return render_template('index.html') + +@app.route('/peers') +def peers(): + """Serve peers page""" + return render_template('peers.html') + +@app.route('/api/palladium/info') +def palladium_info(): + """Get Palladium node blockchain info""" + try: + blockchain_info = palladium_rpc_call('getblockchaininfo') + network_info = palladium_rpc_call('getnetworkinfo') + mining_info = palladium_rpc_call('getmininginfo') + peer_info = palladium_rpc_call('getpeerinfo') + mempool_info = palladium_rpc_call('getmempoolinfo') + + data = { + 'blockchain': blockchain_info or {}, + 'network': network_info or {}, + 'mining': mining_info or {}, + 'peers': len(peer_info) if peer_info else 0, + 'mempool': mempool_info or {}, + 'timestamp': datetime.now().isoformat() + } + + return jsonify(data) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/palladium/peers') +def palladium_peers(): + """Get detailed peer information""" + try: + peer_info = palladium_rpc_call('getpeerinfo') + if not peer_info: + return jsonify({'peers': []}) + + peers_data = [] + for peer in peer_info: + peers_data.append({ + 'addr': peer.get('addr', 'Unknown'), + 'inbound': peer.get('inbound', False), + 'version': peer.get('subver', 'Unknown'), + 'conntime': peer.get('conntime', 0), + 'bytessent': peer.get('bytessent', 0), + 'bytesrecv': peer.get('bytesrecv', 0) + }) + + return jsonify({ + 'peers': peers_data, + 'total': len(peers_data), + 'timestamp': datetime.now().isoformat() + }) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/palladium/blocks/recent') +def recent_blocks(): + """Get recent blocks information""" + try: + blockchain_info = palladium_rpc_call('getblockchaininfo') + if not blockchain_info: + return jsonify({'error': 'Cannot get blockchain info'}), 500 + + current_height = blockchain_info.get('blocks', 0) + blocks = [] + + # Get last 10 blocks + for i in range(min(10, current_height)): + height = current_height - i + block_hash = palladium_rpc_call('getblockhash', [height]) + if block_hash: + block = palladium_rpc_call('getblock', [block_hash]) + if block: + blocks.append({ + 'height': height, + 'hash': block_hash, + 'time': block.get('time'), + 'size': block.get('size'), + 'tx_count': len(block.get('tx', [])) + }) + + return jsonify({'blocks': blocks}) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/electrumx/stats') +def electrumx_stats(): + """Get ElectrumX server statistics""" + try: + stats = get_electrumx_stats() + if stats: + # Get additional info from logs if available + try: + # Try to get container stats + import subprocess + result = subprocess.run( + ['docker', 'exec', 'electrumx-server', 'sh', '-c', + 'ps aux | grep electrumx_server | grep -v grep'], + capture_output=True, + text=True, + timeout=2 + ) + if result.returncode == 0: + # Process is running, add placeholder stats + stats['status'] = 'running' + stats['sessions'] = 0 # Will show 0 for now + stats['requests'] = 0 + stats['subs'] = 0 + except: + pass + + return jsonify({ + 'stats': stats, + 'timestamp': datetime.now().isoformat() + }) + return jsonify({'error': 'Cannot connect to ElectrumX'}), 500 + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/system/resources') +def system_resources(): + """Get system resource usage""" + try: + cpu_percent = psutil.cpu_percent(interval=1) + memory = psutil.virtual_memory() + disk = psutil.disk_usage('/') + + data = { + 'cpu': { + 'percent': cpu_percent, + 'count': psutil.cpu_count() + }, + 'memory': { + 'total': memory.total, + 'used': memory.used, + 'percent': memory.percent + }, + 'disk': { + 'total': disk.total, + 'used': disk.used, + 'percent': disk.percent + }, + 'timestamp': datetime.now().isoformat() + } + + return jsonify(data) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/health') +def health(): + """Health check endpoint""" + palladium_ok = palladium_rpc_call('getblockchaininfo') is not None + electrumx_ok = get_electrumx_stats() is not None + + return jsonify({ + 'status': 'healthy' if (palladium_ok and electrumx_ok) else 'degraded', + 'services': { + 'palladium': 'up' if palladium_ok else 'down', + 'electrumx': 'up' if electrumx_ok else 'down' + }, + 'timestamp': datetime.now().isoformat() + }) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=False) diff --git a/web-dashboard/requirements.txt b/web-dashboard/requirements.txt new file mode 100644 index 0000000..5259973 --- /dev/null +++ b/web-dashboard/requirements.txt @@ -0,0 +1,5 @@ +Flask==3.0.0 +flask-cors==4.0.0 +requests==2.31.0 +psutil==5.9.6 +python-dateutil==2.8.2 diff --git a/web-dashboard/static/dashboard.js b/web-dashboard/static/dashboard.js new file mode 100644 index 0000000..1036831 --- /dev/null +++ b/web-dashboard/static/dashboard.js @@ -0,0 +1,271 @@ +// Dashboard JavaScript + +// Format numbers +function formatNumber(num) { + if (num >= 1000000000) return (num / 1000000000).toFixed(2) + 'B'; + if (num >= 1000000) return (num / 1000000).toFixed(2) + 'M'; + if (num >= 1000) return (num / 1000).toFixed(2) + 'K'; + return num.toString(); +} + +// Format difficulty (always with M suffix and 1 decimal) +function formatDifficulty(num) { + if (num >= 1000000000) return (num / 1000000000).toFixed(1) + ' B'; + if (num >= 1000000) return (num / 1000000).toFixed(1) + ' M'; + if (num >= 1000) return (num / 1000).toFixed(1) + ' K'; + return num.toFixed(1); +} + +// Format block height (always full integer with thousands separator) +function formatBlockHeight(num) { + return num.toLocaleString('en-US'); +} + +// Format bytes +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +// Format time +function formatTime(timestamp) { + const date = new Date(timestamp * 1000); + return date.toLocaleTimeString(); +} + +// Format duration +function formatDuration(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) return `${days}d ${hours}h`; + if (hours > 0) return `${hours}h ${minutes}m`; + return `${minutes}m`; +} + +// Update health status +function updateHealthStatus(data) { + const statusEl = document.getElementById('healthStatus'); + const statusDot = statusEl.querySelector('.status-dot'); + const statusText = statusEl.querySelector('.status-text'); + + if (data.status === 'healthy') { + statusEl.classList.remove('degraded'); + statusText.textContent = 'All Systems Operational'; + } else { + statusEl.classList.add('degraded'); + statusText.textContent = 'Service Degraded'; + } +} + +// Update system resources +async function updateSystemResources() { + try { + const response = await fetch('/api/system/resources'); + const data = await response.json(); + + if (data.error) { + console.error('System resources error:', data.error); + return; + } + + // Update CPU + const cpuPercent = data.cpu.percent.toFixed(1); + document.getElementById('cpuValue').textContent = cpuPercent + '%'; + document.getElementById('cpuProgress').style.width = cpuPercent + '%'; + + // Update Memory + const memPercent = data.memory.percent.toFixed(1); + document.getElementById('memoryValue').textContent = memPercent + '%'; + document.getElementById('memoryProgress').style.width = memPercent + '%'; + + // Update Disk + const diskPercent = data.disk.percent.toFixed(1); + document.getElementById('diskValue').textContent = diskPercent + '%'; + document.getElementById('diskProgress').style.width = diskPercent + '%'; + + } catch (error) { + console.error('Error fetching system resources:', error); + } +} + +// Update Palladium info +async function updatePalladiumInfo() { + try { + const response = await fetch('/api/palladium/info'); + const data = await response.json(); + + if (data.error) { + console.error('Palladium info error:', data.error); + return; + } + + // Update blockchain info + if (data.blockchain) { + document.getElementById('blockHeight').textContent = formatBlockHeight(data.blockchain.blocks || 0); + document.getElementById('difficulty').textContent = formatDifficulty(data.blockchain.difficulty || 0); + document.getElementById('network').textContent = (data.blockchain.chain || 'unknown').toUpperCase(); + + const progress = ((data.blockchain.verificationprogress || 0) * 100).toFixed(2); + document.getElementById('syncProgress').textContent = progress + '%'; + } + + // Update network info + if (data.network) { + let version = data.network.subversion || 'Unknown'; + // Extract version number from /Palladium:2.0.0/ format + const match = version.match(/:([\d.]+)/); + if (match) { + version = 'v' + match[1]; + } + document.getElementById('nodeVersion').textContent = version; + } + + // Update connections + document.getElementById('connections').textContent = data.peers || 0; + + // Update mempool info + if (data.mempool) { + document.getElementById('mempoolSize').textContent = data.mempool.size || 0; + document.getElementById('mempoolBytes').textContent = formatBytes(data.mempool.bytes || 0); + document.getElementById('mempoolMax').textContent = formatBytes(data.mempool.maxmempool || 0); + + // Calculate usage percentage + if (data.mempool.maxmempool && data.mempool.bytes) { + const usage = ((data.mempool.bytes / data.mempool.maxmempool) * 100).toFixed(1); + document.getElementById('mempoolUsage').textContent = usage + '%'; + } else { + document.getElementById('mempoolUsage').textContent = '0%'; + } + } + + } catch (error) { + console.error('Error fetching Palladium info:', error); + } +} + +// Update recent blocks +async function updateRecentBlocks() { + try { + const response = await fetch('/api/palladium/blocks/recent'); + const data = await response.json(); + + if (data.error) { + console.error('Recent blocks error:', data.error); + return; + } + + const tbody = document.getElementById('recentBlocksTable'); + tbody.innerHTML = ''; + + if (data.blocks && data.blocks.length > 0) { + data.blocks.forEach(block => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${block.height} + ${block.hash.substring(0, 20)}... + ${formatTime(block.time)} + ${formatBytes(block.size)} + ${block.tx_count} + `; + tbody.appendChild(row); + }); + } else { + tbody.innerHTML = 'No blocks available'; + } + + } catch (error) { + console.error('Error fetching recent blocks:', error); + } +} + +// Peers are now on a separate page, no update needed here + +// Update ElectrumX stats +async function updateElectrumXStats() { + try { + const response = await fetch('/api/electrumx/stats'); + const data = await response.json(); + + if (data.error) { + console.error('ElectrumX stats error:', data.error); + return; + } + + if (data.stats) { + // Server version (extract version number like we do for node) + let serverVersion = data.stats.server_version || 'Unknown'; + const versionMatch = serverVersion.match(/([\d.]+)/); + if (versionMatch) { + serverVersion = 'v' + versionMatch[1]; + } + document.getElementById('serverVersion').textContent = serverVersion; + + // Active sessions + const sessions = typeof data.stats.sessions === 'number' ? data.stats.sessions : '--'; + document.getElementById('activeSessions').textContent = sessions; + + // Database size + const dbSize = data.stats.db_size > 0 ? formatBytes(data.stats.db_size) : '--'; + document.getElementById('dbSize').textContent = dbSize; + + // Uptime + const uptime = data.stats.uptime > 0 ? formatDuration(data.stats.uptime) : '--'; + document.getElementById('uptime').textContent = uptime; + + // Server IP + document.getElementById('serverIP').textContent = data.stats.server_ip || '--'; + + // TCP Port + document.getElementById('tcpPort').textContent = data.stats.tcp_port || 50001; + + // SSL Port + document.getElementById('sslPort').textContent = data.stats.ssl_port || 50002; + } + + } catch (error) { + console.error('Error fetching ElectrumX stats:', error); + } +} + +// Update health check +async function updateHealth() { + try { + const response = await fetch('/api/health'); + const data = await response.json(); + updateHealthStatus(data); + } catch (error) { + console.error('Error fetching health:', error); + } +} + +// Update last update time +function updateLastUpdateTime() { + const now = new Date().toLocaleString(); + document.getElementById('lastUpdate').textContent = now; +} + +// Update all data +async function updateAll() { + updateLastUpdateTime(); + await Promise.all([ + updateHealth(), + updateSystemResources(), + updatePalladiumInfo(), + updateRecentBlocks(), + updateElectrumXStats() + ]); +} + +// Initialize dashboard +document.addEventListener('DOMContentLoaded', async () => { + // Initial update + await updateAll(); + + // Auto-refresh every 10 seconds + setInterval(updateAll, 10000); +}); diff --git a/web-dashboard/static/peers.js b/web-dashboard/static/peers.js new file mode 100644 index 0000000..5211efd --- /dev/null +++ b/web-dashboard/static/peers.js @@ -0,0 +1,123 @@ +// Peers Page JavaScript + +// Format bytes +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +} + +// Format duration +function formatDuration(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) return `${days}d ${hours}h ${minutes}m`; + if (hours > 0) return `${hours}h ${minutes}m`; + return `${minutes}m`; +} + +// Update peers table and statistics +async function updatePeers() { + try { + const response = await fetch('/api/palladium/peers'); + const data = await response.json(); + + if (data.error) { + console.error('Peers error:', data.error); + return; + } + + const tbody = document.getElementById('peersTableBody'); + tbody.innerHTML = ''; + + if (data.peers && data.peers.length > 0) { + let inboundCount = 0; + let outboundCount = 0; + let totalSent = 0; + let totalReceived = 0; + + data.peers.forEach(peer => { + const row = document.createElement('tr'); + const direction = peer.inbound ? '⬇️ Inbound' : '⬆️ Outbound'; + const directionClass = peer.inbound ? 'peer-inbound' : 'peer-outbound'; + + // Count inbound/outbound + if (peer.inbound) { + inboundCount++; + } else { + outboundCount++; + } + + // Sum traffic + totalSent += peer.bytessent || 0; + totalReceived += peer.bytesrecv || 0; + + // Extract version number + let version = peer.subver || peer.version || 'Unknown'; + const versionMatch = version.match(/([\d.]+)/); + if (versionMatch) { + version = 'v' + versionMatch[1]; + } + + // Format connection time + const connTime = peer.conntime ? formatDuration(Math.floor(Date.now() / 1000) - peer.conntime) : '--'; + + // Calculate total traffic for this peer + const peerTotal = (peer.bytessent || 0) + (peer.bytesrecv || 0); + + row.innerHTML = ` + ${peer.addr} + ${direction} + ${version} + ${connTime} + ${formatBytes(peer.bytessent || 0)} + ${formatBytes(peer.bytesrecv || 0)} + ${formatBytes(peerTotal)} + `; + tbody.appendChild(row); + }); + + // Update statistics + document.getElementById('totalPeers').textContent = data.peers.length; + document.getElementById('inboundPeers').textContent = inboundCount; + document.getElementById('outboundPeers').textContent = outboundCount; + document.getElementById('totalTraffic').textContent = formatBytes(totalSent + totalReceived); + + } else { + tbody.innerHTML = 'No peers connected'; + document.getElementById('totalPeers').textContent = '0'; + document.getElementById('inboundPeers').textContent = '0'; + document.getElementById('outboundPeers').textContent = '0'; + document.getElementById('totalTraffic').textContent = '0 B'; + } + + } catch (error) { + console.error('Error fetching peers:', error); + document.getElementById('peersTableBody').innerHTML = 'Error loading peers'; + } +} + +// Update last update time +function updateLastUpdateTime() { + const now = new Date().toLocaleString(); + document.getElementById('lastUpdate').textContent = now; +} + +// Update all data +async function updateAll() { + updateLastUpdateTime(); + await updatePeers(); +} + +// Initialize page +document.addEventListener('DOMContentLoaded', async () => { + // Initial update + await updateAll(); + + // Auto-refresh every 10 seconds + setInterval(updateAll, 10000); +}); diff --git a/web-dashboard/static/style.css b/web-dashboard/static/style.css new file mode 100644 index 0000000..7fe7100 --- /dev/null +++ b/web-dashboard/static/style.css @@ -0,0 +1,506 @@ +:root { + --primary-color: #6366f1; + --secondary-color: #8b5cf6; + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-card: #1e293b; + --text-primary: #f1f5f9; + --text-secondary: #94a3b8; + --border-color: #334155; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; + background: linear-gradient(135deg, var(--bg-primary) 0%, #1a1f35 100%); + color: var(--text-primary); + min-height: 100vh; + line-height: 1.6; +} + +.container { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +/* Header */ +.header { + background: var(--bg-card); + border-radius: 12px; + padding: 24px; + margin-bottom: 24px; + box-shadow: var(--shadow-lg); + border: 1px solid var(--border-color); +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 16px; +} + +.header h1 { + font-size: 28px; + font-weight: 700; + display: flex; + align-items: center; + gap: 12px; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.logo-icon { + width: 36px; + height: 36px; + stroke: var(--primary-color); + stroke-width: 2; +} + +.health-status { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(16, 185, 129, 0.1); + border-radius: 20px; + border: 1px solid var(--success-color); +} + +.status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--success-color); + animation: pulse 2s ease-in-out infinite; +} + +.status-text { + font-size: 14px; + font-weight: 600; + color: var(--success-color); +} + +.health-status.degraded { + background: rgba(239, 68, 68, 0.1); + border-color: var(--danger-color); +} + +.health-status.degraded .status-dot { + background: var(--danger-color); +} + +.health-status.degraded .status-text { + color: var(--danger-color); +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* Dashboard Grid */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 24px; + margin-bottom: 24px; +} + +.card { + background: var(--bg-card); + border-radius: 12px; + padding: 24px; + box-shadow: var(--shadow); + border: 1px solid var(--border-color); + transition: transform 0.2s, box-shadow 0.2s; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg); +} + +.card.full-width { + grid-column: 1 / -1; +} + +.card.chart-card { + min-height: 350px; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 2px solid var(--border-color); +} + +.card-header h2 { + font-size: 20px; + font-weight: 600; + color: var(--text-primary); +} + +.card-icon { + font-size: 24px; +} + +.card-content { + color: var(--text-secondary); +} + +/* Stats Grid */ +.stat-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} + +.stat-grid-3col { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; +} + +/* ElectrumX Grid Layout */ +.electrumx-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; +} + +.electrumx-grid .full-width { + grid-column: 1 / -1; +} + +.stat-item { + padding: 16px; + background: rgba(99, 102, 241, 0.05); + border-radius: 8px; + border: 1px solid rgba(99, 102, 241, 0.2); +} + +.stat-link { + text-decoration: none; + color: inherit; + cursor: pointer; + transition: all 0.3s ease; + position: relative; +} + +.stat-link:hover { + background: rgba(99, 102, 241, 0.15); + border-color: rgba(99, 102, 241, 0.4); + transform: translateY(-2px); +} + +.stat-link::after { + content: 'β†’'; + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + font-size: 18px; + color: var(--primary-color); + opacity: 0; + transition: opacity 0.3s ease; +} + +.stat-link:hover::after { + opacity: 1; +} + +.stat-label { + font-size: 13px; + color: var(--text-secondary); + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.stat-value { + font-size: 24px; + font-weight: 700; + color: var(--text-primary); + word-break: break-word; +} + +/* Resource Monitoring */ +.resource-item { + display: grid; + grid-template-columns: 120px 1fr 80px; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.resource-item:last-child { + margin-bottom: 0; +} + +.resource-label { + font-size: 14px; + color: var(--text-secondary); + font-weight: 500; +} + +.resource-value { + font-size: 16px; + font-weight: 700; + color: var(--text-primary); + text-align: right; +} + +.progress-bar { + height: 8px; + background: var(--bg-primary); + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + border-radius: 4px; + transition: width 0.5s ease; + position: relative; + overflow: hidden; +} + +.progress-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Table */ +.table-container { + overflow-x: auto; +} + +.blocks-table { + width: 100%; + border-collapse: collapse; +} + +.blocks-table thead { + background: rgba(99, 102, 241, 0.1); +} + +.blocks-table th { + padding: 12px; + text-align: left; + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 2px solid var(--border-color); +} + +.blocks-table td { + padding: 12px; + border-bottom: 1px solid var(--border-color); + font-size: 14px; +} + +.blocks-table tbody tr { + transition: background 0.2s; +} + +.blocks-table tbody tr:hover { + background: rgba(99, 102, 241, 0.05); +} + +.blocks-table .hash-cell { + font-family: 'Courier New', monospace; + font-size: 12px; + color: var(--primary-color); + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.blocks-table .loading { + text-align: center; + padding: 24px; + color: var(--text-secondary); +} + +.peer-addr { + font-family: 'Courier New', monospace; + font-size: 13px; +} + +.peer-inbound { + padding: 4px 8px; + background: rgba(16, 185, 129, 0.1); + border-radius: 4px; + color: var(--success-color); + font-size: 12px; + font-weight: 600; +} + +.peer-outbound { + padding: 4px 8px; + background: rgba(99, 102, 241, 0.1); + border-radius: 4px; + color: var(--primary-color); + font-size: 12px; + font-weight: 600; +} + +/* Back Button */ +.back-button { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: rgba(99, 102, 241, 0.1); + border-radius: 8px; + border: 1px solid var(--primary-color); + color: var(--primary-color); + text-decoration: none; + font-size: 14px; + font-weight: 600; + transition: all 0.3s ease; +} + +.back-button:hover { + background: rgba(99, 102, 241, 0.2); + transform: translateX(-4px); +} + +/* Peers Table Specific */ +.peers-table td { + white-space: nowrap; +} + +.peers-table .peer-addr { + font-family: 'Courier New', monospace; + font-size: 13px; + color: var(--text-primary); +} + +/* Footer */ +.footer { + text-align: center; + padding: 24px; + color: var(--text-secondary); + font-size: 14px; + background: var(--bg-card); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.footer p { + margin: 4px 0; +} + +#lastUpdate { + color: var(--primary-color); + font-weight: 600; +} + +/* Chart Styling */ +.chart-description { + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 12px; + padding: 8px 12px; + background: rgba(99, 102, 241, 0.05); + border-left: 3px solid var(--primary-color); + border-radius: 4px; + font-style: italic; +} + +canvas { + max-height: 280px !important; +} + +/* Responsive */ +@media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + } + + .header-content { + flex-direction: column; + align-items: flex-start; + } + + .stat-grid { + grid-template-columns: 1fr; + } + + .stat-grid-3col { + grid-template-columns: 1fr; + } + + .electrumx-grid { + grid-template-columns: 1fr; + } + + .resource-item { + grid-template-columns: 1fr; + gap: 8px; + } + + .resource-value { + text-align: left; + } + + .blocks-table { + font-size: 12px; + } + + .blocks-table th, + .blocks-table td { + padding: 8px; + } +} + +/* Loading Animation */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.card { + animation: fadeIn 0.5s ease-out; +} diff --git a/web-dashboard/templates/index.html b/web-dashboard/templates/index.html new file mode 100644 index 0000000..183b5d8 --- /dev/null +++ b/web-dashboard/templates/index.html @@ -0,0 +1,200 @@ + + + + + + Palladium & ElectrumX Dashboard + + + +
+ +
+
+

+ + + + Palladium Network Dashboard +

+
+ + Checking... +
+
+
+ + +
+ +
+
+

System Resources

+ πŸ’» +
+
+
+
CPU Usage
+
+
+
+
--%
+
+
+
Memory Usage
+
+
+
+
--%
+
+
+
Disk Usage
+
+
+
+
--%
+
+
+
+ + +
+
+

Palladium Node

+ ⛏️ +
+
+
+
+
Block Height
+
--
+
+
+
Difficulty
+
--
+
+ +
Connections
+
--
+
+
+
Network
+
--
+
+
+
Sync Progress
+
--
+
+
+
Version
+
--
+
+
+
+
+ + +
+
+

ElectrumX Server

+ ⚑ +
+
+
+
+
Server Version
+
--
+
+
+
Active Sessions
+
--
+
+
+
Database Size
+
--
+
+
+
Uptime
+
--
+
+
+
Server IP
+
--
+
+
+
TCP Port
+
--
+
+
+
SSL Port
+
--
+
+
+
+
+ + +
+
+

Mempool

+ πŸ“ +
+
+
+
+
Transactions
+
--
+
+
+
Total Size
+
--
+
+
+
Usage
+
--
+
+
+
Max Size
+
--
+
+
+
+
+ + +
+
+

Recent Blocks

+ πŸ”— +
+
+
+ + + + + + + + + + + + + +
HeightHashTimeSizeTransactions
Loading...
+
+
+
+
+ + +
+

Last updated: --

+

Auto-refresh every 10 seconds

+
+
+ + + + diff --git a/web-dashboard/templates/peers.html b/web-dashboard/templates/peers.html new file mode 100644 index 0000000..e08478d --- /dev/null +++ b/web-dashboard/templates/peers.html @@ -0,0 +1,93 @@ + + + + + + Network Peers - Palladium Dashboard + + + +
+ +
+
+

+ + + + Network Peers +

+ + ← Back to Dashboard + +
+
+ + +
+
+
+

Connection Statistics

+ πŸ“Š +
+
+
+
+
Total Peers
+
--
+
+
+
Inbound
+
--
+
+
+
Outbound
+
--
+
+
+
Total Traffic
+
--
+
+
+
+
+
+ + +
+
+

Connected Peers

+ 🌐 +
+
+
+ + + + + + + + + + + + + + + +
IP AddressDirectionVersionConnection TimeData SentData ReceivedTotal Traffic
Loading peers...
+
+
+
+ + +
+

Last updated: --

+

Auto-refresh every 10 seconds

+
+
+ + + +