Sync build v0.6.79
Automated sync from private repository. Commit: b73bd1649d6337a28052f0e296c9d09fa9f03641
@@ -0,0 +1,66 @@
|
||||
# Installing pyMC Console UI
|
||||
|
||||
This guide covers standalone UI installation. For full pyMC Console with manage.sh installer, see [README.md](README.md).
|
||||
|
||||
## Quick Install (Standalone UI)
|
||||
|
||||
Download and extract the latest release to your pyMC_Repeater's web directory:
|
||||
|
||||
```bash
|
||||
# Download the latest release
|
||||
cd /tmp
|
||||
wget https://github.com/dmduran12/pymc_console/releases/latest/download/pymc-ui-latest.tar.gz
|
||||
|
||||
# Extract to pyMC_Repeater's web directory
|
||||
sudo mkdir -p /opt/pymc_repeater/repeater/web/html
|
||||
sudo tar -xzf pymc-ui-latest.tar.gz -C /opt/pymc_repeater/repeater/web/html/
|
||||
|
||||
# Set proper permissions
|
||||
sudo chown -R repeater:repeater /opt/pymc_repeater/repeater/web/html
|
||||
|
||||
# Clean up
|
||||
rm pymc-ui-latest.tar.gz
|
||||
```
|
||||
|
||||
The dashboard will be served at `http://<your-pi-ip>:8000/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Update
|
||||
|
||||
To update an existing installation to the latest version:
|
||||
|
||||
```bash
|
||||
# Download latest version
|
||||
cd /tmp
|
||||
wget https://github.com/dmduran12/pymc_console/releases/latest/download/pymc-ui-latest.tar.gz
|
||||
|
||||
# Backup and update
|
||||
sudo cp -r /opt/pymc_repeater/repeater/web/html /opt/pymc_repeater/repeater/web/html.backup
|
||||
sudo rm -rf /opt/pymc_repeater/repeater/web/html/*
|
||||
sudo tar -xzf pymc-ui-latest.tar.gz -C /opt/pymc_repeater/repeater/web/html/
|
||||
sudo chown -R repeater:repeater /opt/pymc_repeater/repeater/web/html
|
||||
|
||||
# Clean up
|
||||
rm pymc-ui-latest.tar.gz
|
||||
```
|
||||
|
||||
## Specific Version
|
||||
|
||||
To install a specific version:
|
||||
|
||||
```bash
|
||||
# Replace v0.2.0 with desired version
|
||||
wget https://github.com/dmduran12/pymc_console/releases/download/v0.2.0/pymc-ui-v0.2.0.tar.gz
|
||||
sudo tar -xzf pymc-ui-v0.2.0.tar.gz -C /opt/pymc_repeater/repeater/web/html/
|
||||
```
|
||||
|
||||
## Using manage.sh (Recommended)
|
||||
|
||||
For most users, use the manage.sh installer which handles everything automatically:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/dmduran12/pymc_console.git
|
||||
cd pymc_console
|
||||
sudo ./manage.sh install
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Danny Duran
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,2 +1,303 @@
|
||||
# pymc_console
|
||||
test Sat Dec 27 21:51:52 PST 2025
|
||||
# pyMC Console
|
||||
|
||||
[](https://github.com/dmduran12/pymc_console/releases)
|
||||
[](LICENSE)
|
||||
|
||||
A modern web dashboard for monitoring and managing your [MeshCore](https://meshcore.co.uk/) LoRa mesh repeater.
|
||||
|
||||
Built on [pyMC_Repeater](https://github.com/rightup/pyMC_Repeater) by [RightUp](https://github.com/rightup), pyMC Console provides real-time visibility into your mesh network with an intuitive, feature-rich interface.
|
||||
|
||||
## Features
|
||||
|
||||
### Dashboard
|
||||
- **Live packet metrics** — Received, forwarded, dropped packets with sparkline charts
|
||||
- **TX Delay Recommendations** — Slot-based delay optimization with network role classification (edge/relay/hub/backbone)
|
||||
- **LBT Insights widgets** — Channel health, collision risk, noise floor, link quality at a glance
|
||||
- **Time range selector** — View stats from 20 minutes to 7 days
|
||||
- **Recent packets** — Live feed of incoming traffic
|
||||
|
||||

|
||||
|
||||
### Statistics
|
||||
- **Airtime utilization** — RX/TX utilization spectrum with peak and mean metrics
|
||||
- **Link quality polar** — Neighbor signal strength plotted by compass bearing
|
||||
- **Network composition** — Breakdown of repeaters, companions, and room servers
|
||||
- **Packet types treemap** — Distribution of ADVERT, TXT_MSG, ACK, RESPONSE, etc.
|
||||
- **RF noise floor** — Noise floor heatmap showing interference patterns over time
|
||||
- **Prefix disambiguation** — Health metrics for the topology inference system
|
||||
|
||||

|
||||
|
||||
### Contacts & Topology
|
||||
- **Interactive map** — MapLibre GL with dark theme, neighbor positions, and smooth animations
|
||||
- **Mesh topology graph** — Network connections inferred from packet paths
|
||||
- **Deep Analysis** — One-click full topology rebuild from 20K+ packets
|
||||
- **Intelligent disambiguation** — Four-factor scoring resolves prefix collisions
|
||||
- **Edge confidence** — Line thickness scales with observation count
|
||||
- **Animated edges** — Trace-in effect on toggle, smooth fade-out
|
||||
- **Filter toggles** — Solo view for hub nodes or direct neighbors only
|
||||
- **Loop detection** — Identifies redundant paths (double-line rendering)
|
||||
- **Visual identity** — Yellow house icon for local node, indigo rings for neighbors
|
||||
- **Path health panel** — Health scores, weakest links, and latency estimates for observed routes
|
||||
- **Mobile node detection** — Identifies volatile nodes that appear/disappear frequently
|
||||
|
||||

|
||||
|
||||
### Packets
|
||||
- **Searchable history** — Filter by type, route, time range
|
||||
- **Packet details** — Hash, path, payload, signal info, duplicates
|
||||
- **Path visualization** — Interactive map showing packet route with hop confidence
|
||||
|
||||
### Settings
|
||||
- **Mode toggle** — Forward (repeating) or Monitor (RX only)
|
||||
- **Duty cycle** — Enable/disable enforcement
|
||||
- **Radio config** — Live frequency, power, SF, bandwidth changes
|
||||
|
||||

|
||||
|
||||
### System & Logs
|
||||
- **System resources** — 20-minute rolling CPU and memory utilization chart
|
||||
- **Disk usage** — Storage utilization with progress bar
|
||||
- **Temperature** — Multi-sensor temperature gauges with threshold coloring
|
||||
- **Live logs** — Stream from repeater daemon with DEBUG/INFO toggle
|
||||
|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
- Raspberry Pi (3, 4, 5, or Zero 2 W)
|
||||
- LoRa module (SX1262 or SX1276 based)
|
||||
- Raspbian/Raspberry Pi OS (Bookworm recommended)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone this repository
|
||||
git clone https://github.com/dmduran12/pymc_console.git
|
||||
cd pymc_console
|
||||
|
||||
# Run the installer (requires sudo)
|
||||
sudo bash manage.sh install
|
||||
```
|
||||
|
||||
The installer will:
|
||||
1. Install all dependencies
|
||||
2. Set up [pyMC_Repeater](https://github.com/rightup/pyMC_Repeater)
|
||||
3. Deploy the web dashboard
|
||||
4. Configure the systemd service
|
||||
|
||||
Once complete, access your dashboard at `http://<your-pi-ip>:8000`
|
||||
|
||||
## Management Menu
|
||||
|
||||
After installation, run `sudo bash manage.sh` to access the management menu:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ pyMC Console Management │
|
||||
├─────────────────────────────────────┤
|
||||
│ 1. Start Service │
|
||||
│ 2. Stop Service │
|
||||
│ 3. Restart Service │
|
||||
│ 4. View Logs │
|
||||
│ 5. Configure Radio │
|
||||
│ 6. Configure GPIO │
|
||||
│ 7. Upgrade │
|
||||
│ 8. Uninstall │
|
||||
│ 9. Exit │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Menu Options
|
||||
|
||||
- **Start/Stop/Restart** — Control the repeater service
|
||||
- **View Logs** — Live log output from the repeater
|
||||
- **Configure Radio** — Set frequency, power, bandwidth, SF via preset selection
|
||||
- **Configure GPIO** — Set up SPI bus and GPIO pins for your LoRa module
|
||||
- **Upgrade** — Pull latest updates and reinstall
|
||||
- **Uninstall** — Remove the installation completely
|
||||
|
||||
## Upgrading
|
||||
|
||||
To update to the latest version:
|
||||
|
||||
```bash
|
||||
cd pymc_console
|
||||
git pull
|
||||
sudo bash manage.sh upgrade
|
||||
```
|
||||
|
||||
Or use the TUI menu: `sudo bash manage.sh` → **Upgrade**
|
||||
|
||||
## Configuration
|
||||
|
||||
### Radio Settings
|
||||
|
||||
Use the **Configure Radio** menu option, or edit directly:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/pymc_repeater/config.yaml
|
||||
```
|
||||
|
||||
Key settings:
|
||||
```yaml
|
||||
radio:
|
||||
frequency: 927875000 # Frequency in Hz
|
||||
spreading_factor: 7 # SF7-SF12
|
||||
bandwidth: 62500 # Bandwidth in Hz
|
||||
tx_power: 28 # TX power in dBm
|
||||
coding_rate: 6 # 4/5, 4/6, 4/7, or 4/8
|
||||
```
|
||||
|
||||
### Service Management
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
sudo systemctl status pymc-repeater
|
||||
|
||||
# Start/stop/restart
|
||||
sudo systemctl start pymc-repeater
|
||||
sudo systemctl stop pymc-repeater
|
||||
sudo systemctl restart pymc-repeater
|
||||
|
||||
# View live logs
|
||||
sudo journalctl -u pymc-repeater -f
|
||||
```
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- **Raspberry Pi** (3, 4, 5, or Zero 2 W recommended)
|
||||
- **LoRa Module** — SX1262 or SX1276 based (e.g., Waveshare SX1262, LILYGO T3S3)
|
||||
- **SPI Connection** — Module connected via SPI with GPIO for reset/busy/DIO1
|
||||
|
||||
### Tested Modules
|
||||
|
||||
- Waveshare SX1262 HAT
|
||||
- LILYGO T3S3 (via USB serial)
|
||||
- Ebyte E22 modules
|
||||
- Heltec LoRa 32
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service won't start
|
||||
|
||||
```bash
|
||||
# Check for errors
|
||||
sudo journalctl -u pymc-repeater -n 50
|
||||
|
||||
# Verify config syntax
|
||||
cat /etc/pymc_repeater/config.yaml
|
||||
```
|
||||
|
||||
### No packets being received
|
||||
|
||||
1. Verify SPI is enabled: `ls /dev/spidev*`
|
||||
2. Check GPIO configuration in manage.sh → Configure GPIO
|
||||
3. Confirm frequency matches your network
|
||||
|
||||
### Dashboard not loading
|
||||
|
||||
1. Verify service is running: `sudo systemctl status pymc-repeater`
|
||||
2. Check if port 8000 is accessible: `curl http://localhost:8000/api/stats`
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```bash
|
||||
cd pymc_console
|
||||
sudo bash manage.sh
|
||||
```
|
||||
|
||||
Select **Uninstall** from the menu. This removes:
|
||||
- `/opt/pymc_repeater` (installation)
|
||||
- `/etc/pymc_repeater` (configuration)
|
||||
- `/var/log/pymc_repeater` (logs)
|
||||
- The systemd service
|
||||
|
||||
## How It Works
|
||||
|
||||
### Mesh Topology Analysis
|
||||
|
||||
The dashboard reconstructs network topology from packet paths. MeshCore packets contain 2-character hex prefixes representing the route through the mesh:
|
||||
|
||||
```
|
||||
Packet path: ["FA", "79", "24", "19"]
|
||||
Origin → Hop1 → Hop2 → Local
|
||||
```
|
||||
|
||||
**The Challenge**: Multiple nodes may share the same 2-char prefix (1 in 256 collision chance). The system uses four-factor scoring inspired by [meshcore-bot](https://github.com/agessaman/meshcore-bot) to resolve ambiguity:
|
||||
|
||||
1. **Position (15%)** — Where in paths does this prefix typically appear?
|
||||
2. **Co-occurrence (15%)** — Which prefixes appear adjacent to this one?
|
||||
3. **Geographic (35%)** — How close is the candidate to anchor points?
|
||||
4. **Recency (35%)** — How recently was this node seen?
|
||||
|
||||
**Key techniques:**
|
||||
|
||||
- **Recency scoring** — Exponential decay `e^(-hours/12)` favors recently-active nodes
|
||||
- **Age filtering** — Nodes not seen in 14 days are excluded from consideration
|
||||
- **Dual-hop anchoring** — Candidates scored by distance to both previous and next hops (a relay must be within RF range of both neighbors)
|
||||
- **Score-weighted redistribution** — Appearance counts redistributed proportionally by combined score
|
||||
- **Source-geographic correlation** — Position-1 prefixes scored by distance from packet origin
|
||||
|
||||
The system loads up to 20,000 packets (~7 days of traffic) to build comprehensive topology evidence.
|
||||
|
||||

|
||||
|
||||
### Edge Rendering
|
||||
|
||||
Topology edges are rendered with visual cues indicating confidence:
|
||||
|
||||
- **Line thickness** — Scales from 1.5px (5 validations) to 10px (100+ validations)
|
||||
- **Validation threshold** — Edges require 5+ certain observations to render
|
||||
- **Certainty conditions** — An edge is "certain" when:
|
||||
- Both endpoints have ≥0.6 confidence (HIGH threshold), OR
|
||||
- The destination has ≥0.9 confidence (VERY_HIGH threshold), OR
|
||||
- It's the last hop to local node
|
||||
- **Inclusion threshold** — Edges require ≥0.4 confidence (MEDIUM threshold) for topology
|
||||
- **Trace animation** — Edges "draw" from point A to B when topology is enabled
|
||||
- **Fade animation** — Edges smoothly fade out when topology is disabled
|
||||
- **Loop edges** — Redundant paths rendered as parallel double-lines in accent color
|
||||
|
||||
### Path Visualization
|
||||
|
||||
Clicking a packet shows its route on a map with confidence indicators:
|
||||
|
||||
- **Green** — 100% confidence (unique prefix, no collision)
|
||||
- **Yellow** — 50-99% confidence (high certainty)
|
||||
- **Orange** — 25-49% confidence (medium certainty)
|
||||
- **Red** — 1-24% confidence (low certainty)
|
||||
- **Gray** — Unknown prefix (not in neighbor list)
|
||||
|
||||
## Development
|
||||
|
||||
See [WARP.md](WARP.md) for architecture details and [RELEASE.md](RELEASE.md) for the release process.
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # Starts dev server at http://localhost:5173
|
||||
npm run typecheck # Run TypeScript type checking
|
||||
npm run build # Production build
|
||||
```
|
||||
|
||||
To connect to a remote Pi during development, create `frontend/.env.local`:
|
||||
|
||||
```env
|
||||
VITE_API_URL=http://<pi-ip>:8000
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT — See [LICENSE](LICENSE)
|
||||
|
||||
## Credits
|
||||
|
||||
Built on the excellent work of:
|
||||
|
||||
- **[RightUp](https://github.com/rightup)** — Creator of pyMC_Repeater, pymc_core, and the MeshCore ecosystem
|
||||
- **[pyMC_Repeater](https://github.com/rightup/pyMC_Repeater)** — Core repeater daemon for LoRa communication and mesh routing
|
||||
- **[pymc_core](https://github.com/rightup/pyMC_core)** — Underlying mesh protocol library
|
||||
- **[meshcore-bot](https://github.com/agessaman/meshcore-bot)** — Inspiration for recency scoring and dual-hop anchor disambiguation
|
||||
- **[MeshCore](https://meshcore.co.uk/)** — The MeshCore project and community
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
# pyMC UI Release Guide
|
||||
|
||||
Complete guide for developers to create and publish new releases of the pyMC UI.
|
||||
|
||||
## Table of Contents
|
||||
- [Quick Release Steps](#quick-release-steps)
|
||||
- [Understanding the Release Process](#understanding-the-release-process)
|
||||
- [Manual Build Process](#manual-build-process)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Version Numbering](#version-numbering)
|
||||
|
||||
---
|
||||
|
||||
## Quick Release Steps
|
||||
|
||||
For experienced developers, here's the TL;DR:
|
||||
|
||||
```bash
|
||||
cd pymc_console/frontend
|
||||
|
||||
# 1. Update version (creates git tag automatically if configured)
|
||||
npm version patch
|
||||
|
||||
# 2. Push changes and tag
|
||||
git push origin main
|
||||
git push origin --tags
|
||||
|
||||
# Done! Check GitHub Actions for build status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Understanding the Release Process
|
||||
|
||||
### What Happens Automatically
|
||||
|
||||
This project uses **GitHub Actions** for continuous integration and deployment:
|
||||
|
||||
#### On Every Push to `main` or `dev`:
|
||||
- ✅ Installs dependencies
|
||||
- ✅ Builds static files (`npm run build:static`)
|
||||
- ✅ Creates versioned `.tar.gz` and `.zip` archives
|
||||
- ✅ Uploads build artifacts to GitHub (available for 30 days)
|
||||
- ❌ Does NOT create a GitHub Release
|
||||
|
||||
#### On Version Tag Push (e.g., `v0.1.1`):
|
||||
- ✅ Everything above, PLUS
|
||||
- ✅ Creates a GitHub Release
|
||||
- ✅ Attaches downloadable archives to the release
|
||||
- ✅ Generates automatic release notes from commits
|
||||
|
||||
### The Workflow File
|
||||
|
||||
Located at: `.github/workflows/build-ui.yml`
|
||||
|
||||
This workflow has two jobs:
|
||||
1. **build** - Runs on all pushes and PRs
|
||||
2. **release** - Only runs when a tag starting with `v` is pushed
|
||||
|
||||
---
|
||||
|
||||
## Detailed Release Steps
|
||||
|
||||
### Step 1: Make Your Changes
|
||||
|
||||
Work on your feature branch as normal:
|
||||
```bash
|
||||
git checkout -b feature/my-new-feature
|
||||
# ... make changes ...
|
||||
git add .
|
||||
git commit -m "feat: add awesome new feature"
|
||||
git push origin feature/my-new-feature
|
||||
```
|
||||
|
||||
Create a PR, get it reviewed, and merge to `main`.
|
||||
|
||||
### Step 2: Decide on Version Number
|
||||
|
||||
Use [Semantic Versioning](https://semver.org/):
|
||||
- **MAJOR** (`1.0.0` → `2.0.0`) - Breaking changes
|
||||
- **MINOR** (`0.1.0` → `0.2.0`) - New features, backwards compatible
|
||||
- **PATCH** (`0.1.1` → `0.1.2`) - Bug fixes only
|
||||
|
||||
### Step 3: Update Version
|
||||
|
||||
Navigate to the frontend directory:
|
||||
```bash
|
||||
cd pymc_console/frontend
|
||||
```
|
||||
|
||||
Run the appropriate npm version command:
|
||||
```bash
|
||||
# For bug fixes
|
||||
npm version patch
|
||||
|
||||
# For new features
|
||||
npm version minor
|
||||
|
||||
# For breaking changes
|
||||
npm version major
|
||||
|
||||
# For pre-releases
|
||||
npm version prerelease --preid=beta
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- Updates `version` in `package.json` and `package-lock.json`
|
||||
- Creates a git commit with message like "0.1.2"
|
||||
- Creates a git tag like `v0.1.2` (if git is configured properly)
|
||||
|
||||
### Step 4: Push Changes and Tags
|
||||
|
||||
Push your version commit:
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Push the tag to trigger the release:
|
||||
```bash
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
Or push a specific tag:
|
||||
```bash
|
||||
git push origin v0.1.2
|
||||
```
|
||||
|
||||
### Step 5: Monitor the Release
|
||||
|
||||
1. Go to your repository on GitHub
|
||||
2. Click the **Actions** tab
|
||||
3. You should see a workflow running for your tag
|
||||
4. Wait for both jobs (build and release) to complete
|
||||
|
||||
### Step 6: Verify the Release
|
||||
|
||||
Once the workflow completes:
|
||||
|
||||
1. Go to the **Releases** section of your GitHub repo
|
||||
2. You should see your new release (e.g., `v0.1.2`)
|
||||
3. It should have two attached files:
|
||||
- `pymc-ui-v0.1.2.tar.gz`
|
||||
- `pymc-ui-v0.1.2.zip`
|
||||
4. Release notes are auto-generated from commit messages
|
||||
|
||||
---
|
||||
|
||||
## Manual Build Process
|
||||
|
||||
If you need to build locally without creating a release:
|
||||
|
||||
```bash
|
||||
cd pymc_console/frontend
|
||||
|
||||
# Install dependencies (first time only)
|
||||
npm install
|
||||
|
||||
# Build static files
|
||||
npm run build:static
|
||||
```
|
||||
|
||||
The output will be in `frontend/dist/` directory.
|
||||
|
||||
### What the Build Does
|
||||
|
||||
1. `vite build` - Compiles React app to static HTML/CSS/JS
|
||||
2. Outputs to `frontend/out/`
|
||||
3. Copies `out/` contents to `frontend/dist/`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Release job was skipped"
|
||||
|
||||
**Problem:** You pushed code but no GitHub Release was created.
|
||||
|
||||
**Solution:** The release job only runs for version tags. Make sure you:
|
||||
1. Created a tag with `npm version` or `git tag v0.1.x`
|
||||
2. Pushed the tag with `git push origin --tags`
|
||||
3. The tag name starts with `v` (e.g., `v0.1.2`, not `0.1.2`)
|
||||
|
||||
### "Build failed: npm ERR! code ELIFECYCLE"
|
||||
|
||||
**Problem:** The build process failed.
|
||||
|
||||
**Solution:**
|
||||
1. Pull the latest code: `git pull origin main`
|
||||
2. Clean install locally: `cd frontend && rm -rf node_modules package-lock.json && npm install`
|
||||
3. Test build locally: `npm run build:static`
|
||||
4. Fix any errors before pushing
|
||||
|
||||
### "Tag already exists"
|
||||
|
||||
**Problem:** You tried to create a tag that already exists.
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Delete local tag
|
||||
git tag -d v0.1.2
|
||||
|
||||
# Delete remote tag (if already pushed)
|
||||
git push origin :refs/tags/v0.1.2
|
||||
|
||||
# Create new tag
|
||||
npm version patch
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
### "Permission denied" when creating release
|
||||
|
||||
**Problem:** GitHub Actions doesn't have permission to create releases.
|
||||
|
||||
**Solution:** The workflow already has `permissions: contents: write` set. Check your repository settings:
|
||||
1. Go to Settings → Actions → General
|
||||
2. Under "Workflow permissions", ensure "Read and write permissions" is selected
|
||||
|
||||
---
|
||||
|
||||
## Version Numbering
|
||||
|
||||
### Semantic Versioning Format
|
||||
|
||||
`MAJOR.MINOR.PATCH` (e.g., `0.1.2`)
|
||||
|
||||
### When to Increment Each Part
|
||||
|
||||
| Change Type | Example | Command | Version Change |
|
||||
|-------------|---------|---------|----------------|
|
||||
| Bug fix | Fix broken map display | `npm version patch` | `0.1.1` → `0.1.2` |
|
||||
| New feature | Add dark mode | `npm version minor` | `0.1.2` → `0.2.0` |
|
||||
| Breaking change | Require new API version | `npm version major` | `0.2.0` → `1.0.0` |
|
||||
| Pre-release | Beta testing | `npm version prerelease --preid=beta` | `0.1.2` → `0.1.3-beta.0` |
|
||||
|
||||
### Pre-release Tags
|
||||
|
||||
For alpha, beta, or release candidate versions:
|
||||
|
||||
```bash
|
||||
# First beta
|
||||
npm version 0.2.0-beta.1
|
||||
|
||||
# Subsequent betas
|
||||
npm version prerelease --preid=beta # 0.2.0-beta.2
|
||||
|
||||
# Release candidate
|
||||
npm version 0.2.0-rc.1
|
||||
|
||||
# Final release
|
||||
npm version 0.2.0
|
||||
```
|
||||
|
||||
Pre-releases are automatically marked as "Pre-release" on GitHub.
|
||||
|
||||
---
|
||||
|
||||
## End User Deployment
|
||||
|
||||
Once a release is published, end users can deploy it:
|
||||
|
||||
### Download and Extract
|
||||
|
||||
```bash
|
||||
# Download latest release
|
||||
wget https://github.com/dmduran12/pymc_console/releases/download/v0.2.0/pymc-ui-v0.2.0.tar.gz
|
||||
|
||||
# Extract to pyMC_Repeater's web directory
|
||||
tar -xzf pymc-ui-v0.2.0.tar.gz -C /opt/pymc_repeater/repeater/web/html/
|
||||
|
||||
# Or use zip
|
||||
unzip pymc-ui-v0.2.0.zip -d /opt/pymc_repeater/repeater/web/html/
|
||||
```
|
||||
|
||||
### Configure Backend
|
||||
|
||||
The backend (pyMC_API) should be configured to serve these static files. Users control where they deploy the UI files.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# View current version
|
||||
cat frontend/package.json | grep version
|
||||
|
||||
# List all tags
|
||||
git tag --list
|
||||
|
||||
# View latest tag
|
||||
git describe --tags --abbrev=0
|
||||
|
||||
# Delete local tag
|
||||
git tag -d v0.1.2
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v0.1.2
|
||||
|
||||
# Create tag manually (if npm version didn't work)
|
||||
git tag v0.1.2
|
||||
git push origin v0.1.2
|
||||
|
||||
# Check GitHub Actions status
|
||||
# Visit: https://github.com/dmduran12/pymc_console/actions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check GitHub Actions logs for detailed error messages
|
||||
- Review commit messages to ensure they follow conventions
|
||||
- Test builds locally before pushing tags
|
||||
- Ask in the project's discussion forum or issues section
|
||||
@@ -0,0 +1 @@
|
||||
0.6.79
|
||||
@@ -0,0 +1 @@
|
||||
import{c as e,j as t}from"./index-CylDUtFA.js";import{b as n,c as o}from"./recharts-CGff6_7g.js";const a=e("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]),r=e("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]),s=e("map-pin",[["path",{d:"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0",key:"1r0f0z"}],["circle",{cx:"12",cy:"10",r:"3",key:"ilqhr7"}]]);function c({hash:e,prefixLength:s=8,suffixLength:c=6,full:i=!1,className:d,size:l="md"}){const[p,h]=n.useState(!1),[u,x]=n.useState(!1),m=n.useRef(null),y=n.useCallback(t=>{t.preventDefault(),t.stopPropagation(),m.current&&clearTimeout(m.current),function(e){if("undefined"!=typeof window&&(window.isSecureContext||"https:"===window.location.protocol||"localhost"===window.location.hostname)&&navigator.clipboard&&"function"==typeof navigator.clipboard.writeText)return navigator.clipboard.writeText(e).catch(()=>{}),!0;try{const t=window.scrollX,n=window.scrollY,o=document.createElement("textarea");o.value=e,o.style.position="fixed",o.style.left="-9999px",o.style.top="-9999px",o.style.opacity="0",o.style.pointerEvents="none",o.setAttribute("readonly",""),document.body.appendChild(o),o.focus({preventScroll:!0}),o.select(),o.setSelectionRange(0,e.length);const a=document.execCommand("copy");return document.body.removeChild(o),window.scrollTo(t,n),a}catch(t){return!1}}(e),h(!0),m.current=setTimeout(()=>h(!1),2e3)},[e]),w=i||e.length<=s+c+3?e:`${e.slice(0,s)}…${e.slice(-c)}`,f={xs:"text-[9px] px-1 py-0.5 gap-0.5",sm:"text-[10px] px-1.5 py-0.5 gap-1",md:"text-xs px-2 py-1 gap-1.5"}[l];return t.jsxs("button",{type:"button",onClick:y,onMouseEnter:()=>x(!0),onMouseLeave:()=>x(!1),className:o("inline-flex items-center font-mono rounded-md transition-all duration-200","bg-bg-subtle/80 hover:bg-bg-elevated border border-border-subtle hover:border-border-strong","cursor-pointer select-none",f,d),title:`Click to copy: ${e}`,"aria-label":`Copy hash ${e}`,children:[t.jsx("span",{className:o("transition-colors duration-200",p?"text-accent-success":"text-text-secondary"),children:w}),t.jsx("span",{className:o("flex items-center justify-center transition-all duration-200","xs"===l?"w-2.5 h-2.5":"sm"===l?"w-3 h-3":"w-3.5 h-3.5"),children:p?t.jsx(a,{className:o("text-accent-success","xs"===l?"w-2 h-2":"sm"===l?"w-2.5 h-2.5":"w-3 h-3")}):t.jsx(r,{className:o("transition-opacity duration-200",u?"opacity-70":"opacity-40","xs"===l?"w-2 h-2":"sm"===l?"w-2.5 h-2.5":"w-3 h-3")})})]})}export{r as C,c as H,s as M,a};
|
||||
@@ -0,0 +1 @@
|
||||
import{j as e}from"./index-CylDUtFA.js";import{c as a}from"./recharts-CGff6_7g.js";function l({children:l,className:s}){return e.jsx("div",{className:a("section-gap",s),children:l})}function s({title:a,icon:l,controls:s,subtitle:c}){return e.jsxs("div",{className:"flex flex-col gap-2",children:[e.jsxs("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",children:[e.jsxs("h1",{className:"type-title text-text-primary flex items-center gap-3",children:[l&&e.jsx("span",{className:"w-6 h-6 text-accent-primary flex-shrink-0",children:l}),a]}),s&&e.jsx("div",{className:"flex items-center gap-2 sm:gap-3",children:s})]}),c&&e.jsx("div",{children:c})]})}function c({children:l,className:s}){return e.jsx("div",{className:a("grid-12",s),children:l})}const t=(e,a)=>{const l=a?`${a}:`:"";return"full"===e?`${l}col-span-full`:`${l}col-span-${e}`};function r({children:l,span:s="full",sm:c,md:r,lg:i,xl:n,className:d}){const m=a(t(s),c&&t(c,"sm"),r&&t(r,"md"),i&&t(i,"lg"),n&&t(n,"xl"),d);return e.jsx("div",{className:m,children:l})}const i={sm:"card-sm",md:"card-md",lg:"card-lg",hero:"card-hero",auto:"card-auto"};function n({children:l,size:s,compact:c,noPadding:t,className:r,style:n,onClick:d}){return e.jsx("div",{className:a("glass-card",!t&&(c?"card-padding-sm":"card-padding"),s&&i[s],r),style:n,onClick:d,children:l})}function d({icon:l,title:s,badge:c,subtitle:t,actions:r,iconColor:i="text-accent-primary",largeTitle:n=!1,listHeader:d=!1}){return e.jsxs("div",{className:a("flex flex-col gap-1",d?"px-4 py-3 border-b border-border-subtle bg-bg-elevated/20":"mb-3"),children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[l&&e.jsx("span",{className:a("icon-md",i),children:l}),e.jsx("span",{className:n?"type-subheading text-text-primary":"type-micro",children:s}),c&&e.jsx("span",{className:"pill-tag",children:c})]}),r&&e.jsx("div",{className:"flex items-center gap-2 flex-wrap",children:r})]}),t&&e.jsx("p",{className:"type-label text-text-muted ml-7",children:t})]})}export{n as C,c as G,l as P,s as a,d as b,r as c};
|
||||
@@ -0,0 +1 @@
|
||||
import{j as e}from"./index-CylDUtFA.js";import{b as t}from"./recharts-CGff6_7g.js";import{M as o,S as n,L as r,a,P as i}from"./maplibre-gl-zy3TF1FS.js";import"./maplibre-gl-DsiI67my.js";const s="#4F46E5",l="#F9D26F",d="#B49DFF",c="#39D98A",p="#B49DFF";function u(e,t,o,n=!1,r=!1,a=!1){let i;i=r?c:a?p:e?s:t?"#6366F1":o?"transparent":l;const u=e||t||!o||r||a;return`<div style="\n width: 14px;\n height: 14px;\n background: ${i};\n border-radius: 50%;\n border: ${u?0:5}px solid ${u?"transparent":"#4338CA"};\n box-sizing: border-box;\n transition: box-shadow 0.15s ease;\n ${n?`box-shadow: 0 0 0 3px ${d}40, 0 0 8px ${d}60;`:""}\n "></div>`}const m={id:"path-line",type:"line",paint:{"line-color":"#3B3F4A","line-width":2,"line-opacity":.7},layout:{"line-cap":"round","line-join":"round"}};function x({resolvedPath:d,localNode:x,hubNodes:h=[],hoveredHopIndex:f,onHoverHop:b}){const y=t.useRef(null),g=t.useMemo(()=>new Set(h),[h]),[v,N]=t.useState(null),{positions:C,markers:F,pathLineGeoJSON:k}=t.useMemo(()=>{const e=[],t=[],o=[];d.hops.forEach((n,r)=>{const a=n.candidates.filter(e=>{return t=e.latitude,o=e.longitude,0!==t||0!==o;var t,o});if(0===a.length)return;const i=[...a].sort((e,t)=>t.probability-e.probability)[0];o.push([i.longitude,i.latitude]);const s=!0===n.isSource,l=!0===n.isDestination;a.forEach((o,i)=>{const d=[o.latitude,o.longitude];e.push(d);const c=0===i;t.push({position:d,prefix:n.prefix,confidence:n.confidence,candidateCount:a.length,hopIndex:r,candidate:o,isHub:g.has(o.hash),isPrimary:c,isSource:s,isDestination:l})})});const n={type:"FeatureCollection",features:o.length>=2?[{type:"Feature",properties:{},geometry:{type:"LineString",coordinates:o}}]:[]};return{positions:e,markers:t,pathLineGeoJSON:n}},[d,g]),{center:S,zoom:j}=t.useMemo(()=>{if(0===C.length)return x?{center:[x.longitude,x.latitude],zoom:10}:{center:[0,0],zoom:2};let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[c,p]of C)c<e&&(e=c),c>t&&(t=c),p<o&&(o=p),p>n&&(n=p);const r=(e+t)/2,a=(o+n)/2,i=t-e,s=n-o,l=Math.max(i,s);let d;return d=l<.01?15:l<.05?13:l<.1?12:l<.5?10:l<1?9:l<5?7:5,{center:[a,r],zoom:d}},[C,x]);t.useEffect(()=>{if(!y.current||C.length<2)return;let e=1/0,t=-1/0,o=1/0,n=-1/0;for(const[r,a]of C)r<e&&(e=r),r>t&&(t=r),a<o&&(o=a),a>n&&(n=a);y.current.fitBounds([[o,e],[n,t]],{padding:30,maxZoom:16,duration:0})},[C]);const L=t.useCallback(e=>{N({longitude:e.position[1],latitude:e.position[0],marker:e})},[]);return 0===C.length?e.jsx("div",{className:"h-[200px] flex items-center justify-center text-text-muted text-sm bg-bg-elevated",children:"No mappable path data"}):e.jsxs(o,{ref:y,initialViewState:{longitude:S[0],latitude:S[1],zoom:j},style:{height:"200px",width:"100%"},mapStyle:"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",attributionControl:!1,children:[k.features.length>0&&e.jsx(n,{id:"path-line-source",type:"geojson",data:k,children:e.jsx(r,{...m})}),F.map(t=>{const o=f===t.hopIndex;return e.jsx(a,{longitude:t.position[1],latitude:t.position[0],anchor:"center",onClick:()=>L(t),children:e.jsx("div",{style:{cursor:"pointer",opacity:t.isPrimary?1:.5},onMouseEnter:()=>null==b?void 0:b(t.hopIndex),onMouseLeave:()=>null==b?void 0:b(null),dangerouslySetInnerHTML:{t:u(t.candidate.isLocal||!1,t.isHub,t.isPrimary,o,t.isSource,t.isDestination)}})},`${t.hopIndex}-${t.candidate.hash}`)}),v&&e.jsx(i,{longitude:v.longitude,latitude:v.latitude,anchor:"bottom",offset:[0,-12],closeOnClick:!1,onClose:()=>N(null),className:"maplibre-popup",children:e.jsxs("div",{className:"text-xs",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"font-semibold",children:v.marker.candidate.name}),v.marker.isSource&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:c,color:"#000"},children:"SRC"}),v.marker.isDestination&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:p,color:"#000"},children:"DST"}),v.marker.isHub&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:"#FBBF24",color:"#000"},children:"HUB"}),v.marker.candidate.isLocal&&e.jsx("span",{className:"px-1 py-0.5 text-[8px] font-bold rounded",style:{backgroundColor:s,color:"#fff"},children:"LOCAL"})]}),e.jsxs("div",{className:"text-text-muted font-mono text-[10px]",children:[v.marker.prefix," • ",v.marker.candidate.hash.slice(0,10),"..."]}),!v.marker.isPrimary&&v.marker.candidateCount>1&&e.jsxs("div",{style:{color:l},children:["Alternative (",(100*v.marker.candidate.probability).toFixed(0),"%)"]}),v.marker.isPrimary&&v.marker.candidateCount>1&&e.jsxs("div",{className:"text-text-muted",children:[v.marker.candidateCount," candidates"]})]})})]})}export{x as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as e,j as t}from"./index-CylDUtFA.js";import{c as a,b as s}from"./recharts-CGff6_7g.js";const n=e("signal-high",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}]]),r=e("signal-low",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}]]),c=e("signal-medium",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}]]),i=e("signal-zero",[["path",{d:"M2 20h.01",key:"4haj6o"}]]),o=e("signal",[["path",{d:"M2 20h.01",key:"4haj6o"}],["path",{d:"M7 20v-4",key:"j294jx"}],["path",{d:"M12 20v-8",key:"i3yub9"}],["path",{d:"M17 20V8",key:"1tkaf5"}],["path",{d:"M22 4v16",key:"sih9yq"}]]);function d(e){return e>=-90?"excellent":e>=-100?"good":e>=-110?"fair":e>=-120?"weak":"poor"}function l(e,t){if(!t)return"bg-white/10";switch(e){case"excellent":return"bg-accent-success";case"good":return"bg-[#71F8E5]";case"fair":return"bg-[#F9D26F]";case"weak":return"bg-[#FB923C]";case"poor":return"bg-accent-danger";default:return"bg-white/20"}}function h({rssi:e,className:s="w-4 h-4"}){const l=d(e),h=function(e){switch(e){case"excellent":return"text-accent-success";case"good":return"text-[#71F8E5]";case"fair":return"text-[#F9D26F]";case"weak":return"text-[#FB923C]";case"poor":return"text-accent-danger";default:return"text-text-muted"}}(l),u=a(h,s);switch(l){case"excellent":return t.jsx(o,{className:u});case"good":return t.jsx(n,{className:u});case"fair":return t.jsx(c,{className:u});case"weak":return t.jsx(r,{className:u});default:return t.jsx(i,{className:u})}}const u=s.memo(function({rssi:e,snr:s,compact:n=!1,showValues:r=!0}){const c=d(e),i={excellent:4,good:3,fair:2,weak:1,poor:0}[c];return n?t.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[r&&t.jsx("span",{className:"text-[10px] font-mono text-text-secondary",children:e}),t.jsx("div",{className:"flex items-end gap-[2px] h-3",children:Array.from({length:4}).map((e,s)=>t.jsx("div",{className:a("w-[3px] rounded-[1px] transition-colors",l(c,s<i)),style:{height:(s+1)/4*100+"%"}},s))})]}):t.jsxs("div",{className:"flex items-center justify-end gap-2",children:[r&&t.jsxs("div",{className:"flex flex-col items-end",children:[t.jsxs("span",{className:"text-xs font-mono text-text-secondary leading-tight",children:[e," dBm"]}),void 0!==s&&t.jsxs("span",{className:"text-[10px] font-mono text-text-muted leading-tight",children:[s.toFixed(1)," dB"]})]}),t.jsx("div",{className:"flex items-end gap-[2px] h-3.5",children:Array.from({length:4}).map((e,s)=>t.jsx("div",{className:a("w-[3px] rounded-[1px] transition-colors",l(c,s<i)),style:{height:(s+1)/4*100+"%"}},s))})]})});function x(e){const t=d(e);return t.charAt(0).toUpperCase()+t.slice(1)}export{u as S,h as a,x as g};
|
||||
@@ -0,0 +1 @@
|
||||
import{b as e}from"./recharts-CGff6_7g.js";import{j as t}from"./index-CylDUtFA.js";function o(t,o){const[r,n]=e.useState(t);return e.useEffect(()=>{const e=setTimeout(()=>n(t),o);return()=>clearTimeout(e)},[t,o]),r}const r=e.memo(function({ranges:e,selectedIndex:o,onSelect:r}){return t.jsx("div",{className:"toggle-group flex-shrink-0 overflow-x-auto",children:e.map((e,n)=>t.jsx("button",{onClick:()=>r(n),className:"toggle-group-item "+(o===n?"active":""),children:e.label},e.label))})});export{r as T,o as u};
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const t=a("activity",[["path",{d:"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2",key:"169zse"}]]);export{t as A};
|
||||
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1 @@
|
||||
import{c}from"./index-CylDUtFA.js";const r=c("circle",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]]);export{r as C};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const s=a("house",[["path",{d:"M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8",key:"5wwlr5"}],["path",{d:"M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z",key:"r6nss1"}]]);export{s as H};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as e}from"./index-CylDUtFA.js";const c=e("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);export{c as I};
|
||||
@@ -0,0 +1 @@
|
||||
import"./recharts-CGff6_7g.js";
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const e=a("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);export{e as L};
|
||||
@@ -0,0 +1 @@
|
||||
import{r as t,g as e}from"./maplibre-gl-DsiI67my.js";function r(t,e){for(var r=0;r<e.length;r++){const o=e[r];if("string"!=typeof o&&!Array.isArray(o))for(const e in o)if("default"!==e&&!(e in t)){const r=Object.getOwnPropertyDescriptor(o,e);r&&Object.defineProperty(t,e,r.get?r:{enumerable:!0,get:()=>o[e]})}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var o=t();const n=r({__proto__:null,default:e(o)},[o]);export{n as m};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const e=a("refresh-cw",[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]]);export{e as R};
|
||||
@@ -0,0 +1 @@
|
||||
!function(){"use strict";const n=216e5;function t(n){return n?n.startsWith("0x")||n.startsWith("0X")?n.slice(2,4).toUpperCase():n.slice(0,2).toUpperCase():""}function o(n){if(!n)return null;if(Array.isArray(n))return n;if("string"==typeof n)try{const t=JSON.parse(n);return Array.isArray(t)?t:null}catch{return null}return null}self.onmessage=r=>{const{type:e,payload:c}=r.data;if("compute"!==e)return void self.postMessage({type:"error",error:`Unknown message type: ${e}`});const s=performance.now();try{const{packets:r,nodeHashes:e}=c,f=function(r,e){const c=Date.now(),s=c-6048e5,f=new Map;for(const n of e){const o=t(n);o&&(f.has(o)||f.set(o,new Set),f.get(o).add(n))}const a=new Map,i=new Map;for(const n of e)a.set(n,new Map),i.set(n,c);for(const p of r){const r=p.timestamp??0,e=r>1e12?r:1e3*r;if(e<s)continue;const c=new Set,u=o(p.forwarded_path??p.original_path);if(u&&u.length>0)for(const n of u){const t=String(n).toUpperCase();f.has(t)&&c.add(t)}if((!u||0===u.length)&&p.src_hash){const n=t(p.src_hash);f.has(n)&&c.add(n)}if(c.size>0){const t=Math.floor((e-s)/n),o=Math.max(0,Math.min(27,t));for(const n of c){const t=f.get(n);for(const n of t){const t=a.get(n);t.set(o,(t.get(o)||0)+1),e<i.get(n)&&i.set(n,e)}}}}const u=new Map;for(const t of e){const o=a.get(t),r=i.get(t);if(0===o.size){u.set(t,[]);continue}const e=Math.max(0,Math.floor((r-s)/n)),c=[];for(let t=e;t<28;t++)c.push({idx:t-e,count:o.get(t)||0,timestamp:s+t*n});u.set(t,c)}return u}(r,e),a=performance.now()-s,i={type:"result",payload:{sparklineEntries:Array.from(f.entries())},computeTimeMs:a,packetCount:r.length,nodeCount:e.length};self.postMessage(i)}catch(f){const n={type:"error",error:f instanceof Error?f.message:"Unknown error in sparkline worker"};self.postMessage(n)}}}();
|
||||
@@ -0,0 +1 @@
|
||||
import{c as t}from"./index-CylDUtFA.js";const e=t("network",[["rect",{x:"16",y:"16",width:"6",height:"6",rx:"1",key:"4q2zg0"}],["rect",{x:"2",y:"16",width:"6",height:"6",rx:"1",key:"8cvhb9"}],["rect",{x:"9",y:"2",width:"6",height:"6",rx:"1",key:"1egb70"}],["path",{d:"M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3",key:"1jsf9p"}],["path",{d:"M12 12V8",key:"2874zd"}]]),h=t("trending-up",[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]]);export{e as N,h as T};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const e=a("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);export{e as T};
|
||||
@@ -0,0 +1 @@
|
||||
import{b as r}from"./recharts-CGff6_7g.js";function t(t,e,n=!0,s=!1){const a=r.useRef(t);r.useEffect(()=>{a.current=t},[t]),r.useEffect(()=>{if(!n)return;s||a.current();const r=setInterval(()=>{a.current()},e);return()=>clearInterval(r)},[e,n,s])}export{t as u};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const e=a("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]]);export{e as U};
|
||||
@@ -0,0 +1 @@
|
||||
import{c as a}from"./index-CylDUtFA.js";const t=a("minus",[["path",{d:"M5 12h14",key:"1ays0h"}]]),d=a("trending-down",[["path",{d:"M16 17h6v-6",key:"t6n2it"}],["path",{d:"m22 17-8.5-8.5-5 5L2 7",key:"x473p"}]]),h=a("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]]);export{t as M,d as T,h as Z};
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>pyMC Console</title>
|
||||
<meta name="description" content="LoRa Mesh Network Repeater Monitoring Dashboard" />
|
||||
|
||||
<!-- Fonts: Inter (display) and JetBrains Mono (data) -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,700;1,800;1,900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Theme initialization - prevent flash of unstyled content -->
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
// Try new key first, fall back to legacy key for migration
|
||||
var scheme = localStorage.getItem('pymc-color-scheme');
|
||||
if (!scheme) {
|
||||
// Migration: read legacy key and map to color scheme
|
||||
var legacy = localStorage.getItem('pymc-background');
|
||||
if (legacy && legacy !== 'default') {
|
||||
scheme = legacy; // Legacy stored preset ID which matches scheme ID
|
||||
}
|
||||
}
|
||||
// Apply theme if not default (default uses :root styles)
|
||||
if (scheme && scheme !== 'default') {
|
||||
document.documentElement.setAttribute('data-theme', scheme);
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font-display: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--font-data: 'JetBrains Mono', 'SF Mono', Monaco, monospace;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-CylDUtFA.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/maplibre-gl-DsiI67my.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/recharts-CGff6_7g.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CLg20r2M.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "pymc_console",
|
||||
"version": "0.6.79",
|
||||
"description": "Vite + React Dashboard for pyMC_Repeater",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "vite build",
|
||||
"build:static": "npm run build && npm run package",
|
||||
"package": "rm -rf dist && mkdir -p dist && cp -r out/* dist/",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"clsx": "^2.1.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-react": "^0.559.0",
|
||||
"maplibre-gl": "^5.15.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-is": "^19.2.3",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-map-gl": "^8.1.0",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"recharts": "^3.5.1",
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.25",
|
||||
"globals": "^16.5.0",
|
||||
"tailwindcss": "^4",
|
||||
"terser": "^5.44.1",
|
||||
"typescript": "^5",
|
||||
"typescript-eslint": "^8.50.0",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||