From 427fbc1b4ebe44a43115bef4b4a50b6fd7fb7070 Mon Sep 17 00:00:00 2001 From: zyphlar Date: Wed, 15 Apr 2026 13:45:38 -0700 Subject: [PATCH] Move meshtastic mqtt project to meshcore usb project --- .env.example | 30 +- .env.meshtastic | 27 - DEPLOYMENT.md | 413 --------- PROJECT_SUMMARY.md | 281 ------ QUICKSTART.md | 107 --- README.md | 247 +----- STRUCTURE.txt | 55 -- check-data.js | 49 -- package.json | 14 +- public/css/style.css | 552 ++++-------- public/index.html | 94 +- public/js/app.js | 1733 +++++++++++++------------------------ screenshot-1.png | Bin 0 -> 51930 bytes screenshot-2.png | Bin 0 -> 46883 bytes src/auth/auth.js | 82 +- src/config/config.js | 41 +- src/database/db.js | 124 +-- src/database/queries.js | 343 +++----- src/mqtt/client.js | 405 --------- src/routes/api.js | 345 ++++---- src/scripts/createUser.js | 23 +- src/serial/client.js | 305 +++++++ src/server.js | 81 +- src/services/cron.js | 46 +- src/utils/logger.js | 37 +- test-json.js | 45 - test-nodeinfo.js | 57 -- test-setup.js | 25 - 28 files changed, 1550 insertions(+), 4011 deletions(-) delete mode 100644 .env.meshtastic delete mode 100644 DEPLOYMENT.md delete mode 100644 PROJECT_SUMMARY.md delete mode 100644 QUICKSTART.md delete mode 100644 STRUCTURE.txt delete mode 100644 check-data.js create mode 100644 screenshot-1.png create mode 100644 screenshot-2.png delete mode 100644 src/mqtt/client.js create mode 100644 src/serial/client.js delete mode 100644 test-json.js delete mode 100644 test-nodeinfo.js delete mode 100644 test-setup.js diff --git a/.env.example b/.env.example index 9176b82..4708194 100644 --- a/.env.example +++ b/.env.example @@ -1,32 +1,20 @@ -# Server Configuration +# Server PORT=3000 -NODE_ENV=production +NODE_ENV=development +SESSION_SECRET=change-this-to-a-random-secret -# Session Secret (CHANGE THIS!) -SESSION_SECRET=change-this-to-a-random-secret-string +# MeshCore USB Serial +# Windows: COM3, COM4, etc. +# Linux/Mac: /dev/ttyUSB0, /dev/cu.usbmodem14401, etc. +SERIAL_PORT=COM3 -# MQTT Configuration -MQTT_BROKER=mqtt://mqtt.meshtastic.org -MQTT_PORT=1883 -MQTT_USERNAME=meshdev -MQTT_PASSWORD=large4cats -MQTT_TOPIC=msh/US/# -MQTT_PUB_TOPIC="msh/US/2/json/mqtt/" - -# Optional: If you have your own MQTT broker -# MQTT_BROKER=mqtt://your-broker.com -# MQTT_USERNAME=your-username -# MQTT_PASSWORD=your-password - -# Data Retention (in days) +# Data retention DATA_RETENTION_DAYS=30 - -# Cron schedule for automatic data purging (default: daily at 2 AM) PURGE_CRON_SCHEDULE=0 2 * * * # Logging LOG_LEVEL=info -# Rate Limiting +# Rate limiting RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 diff --git a/.env.meshtastic b/.env.meshtastic deleted file mode 100644 index 1d01595..0000000 --- a/.env.meshtastic +++ /dev/null @@ -1,27 +0,0 @@ -# Server Configuration -PORT=3000 -NODE_ENV=development - -# Session Secret -SESSION_SECRET=test-secret-for-development-only - -# MQTT Configuration (Meshtastic Public Broker) -MQTT_BROKER=mqtt://mqtt.meshtastic.org -MQTT_PORT=1883 -MQTT_USERNAME=meshdev -MQTT_PASSWORD=large4cats -MQTT_TOPIC=# -MQTT_PUB_TOPIC="msh/US/2/json/mqtt/" - -# Data Retention (in days) -DATA_RETENTION_DAYS=30 - -# Cron schedule for automatic data purging (daily at 2 AM) -PURGE_CRON_SCHEDULE=0 2 * * * - -# Logging -LOG_LEVEL=info - -# Rate Limiting -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=1000 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 15bf008..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,413 +0,0 @@ -# Deployment Guide - -This guide covers deploying the Meshtastic MQTT Dashboard to production environments. - -## Pre-Deployment Checklist - -- [ ] Change `SESSION_SECRET` to a strong random string -- [ ] Set `NODE_ENV=production` in `.env` -- [ ] Review and adjust MQTT broker settings -- [ ] Configure data retention policies -- [ ] Set up SSL/TLS certificates -- [ ] Configure firewall rules -- [ ] Plan backup strategy -- [ ] Create initial user accounts -- [ ] Test the application locally - -## Deployment Options - -### Option 1: Traditional Server (Linux) - -#### Requirements -- Ubuntu 20.04+ or similar Linux distribution -- Node.js 16.x or higher -- Nginx (for reverse proxy and SSL) -- 1GB+ RAM -- 10GB+ disk space - -#### Steps - -1. **Install Node.js** -```bash -curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - -sudo apt-get install -y nodejs -``` - -2. **Clone/Upload Application** -```bash -cd /opt -sudo mkdir meshtastic-dashboard -sudo chown $USER:$USER meshtastic-dashboard -cd meshtastic-dashboard -# Upload your files here -``` - -3. **Install Dependencies** -```bash -npm ci --production -``` - -4. **Configure Environment** -```bash -cp .env.example .env -nano .env # Edit configuration -``` - -5. **Create User** -```bash -npm run create-user -``` - -6. **Set Up Systemd Service** - -Create `/etc/systemd/system/meshtastic-dashboard.service`: - -```ini -[Unit] -Description=Meshtastic MQTT Dashboard -After=network.target - -[Service] -Type=simple -User=www-data -WorkingDirectory=/opt/meshtastic-dashboard -Environment=NODE_ENV=production -ExecStart=/usr/bin/node src/server.js -Restart=on-failure -RestartSec=10 - -[Install] -WantedBy=multi-user.target -``` - -Enable and start: -```bash -sudo systemctl daemon-reload -sudo systemctl enable meshtastic-dashboard -sudo systemctl start meshtastic-dashboard -sudo systemctl status meshtastic-dashboard -``` - -7. **Configure Nginx** - -Create `/etc/nginx/sites-available/meshtastic-dashboard`: - -```nginx -server { - listen 80; - server_name your-domain.com; - - # Redirect to HTTPS - return 301 https://$server_name$request_uri; -} - -server { - listen 443 ssl http2; - server_name your-domain.com; - - ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; - - location / { - proxy_pass http://localhost:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_bypass $http_upgrade; - } -} -``` - -Enable site: -```bash -sudo ln -s /etc/nginx/sites-available/meshtastic-dashboard /etc/nginx/sites-enabled/ -sudo nginx -t -sudo systemctl reload nginx -``` - -8. **Set Up SSL with Let's Encrypt** -```bash -sudo apt-get install certbot python3-certbot-nginx -sudo certbot --nginx -d your-domain.com -``` - -### Option 2: Docker Deployment - -Create `Dockerfile`: - -```dockerfile -FROM node:18-alpine - -WORKDIR /app - -COPY package*.json ./ -RUN npm ci --production - -COPY . . - -RUN mkdir -p data logs - -EXPOSE 3000 - -CMD ["node", "src/server.js"] -``` - -Create `docker-compose.yml`: - -```yaml -version: '3.8' - -services: - meshtastic-dashboard: - build: . - ports: - - "3000:3000" - volumes: - - ./data:/app/data - - ./logs:/app/logs - - ./.env:/app/.env:ro - restart: unless-stopped - environment: - - NODE_ENV=production -``` - -Deploy: -```bash -docker-compose up -d -``` - -### Option 3: Cloud Platforms - -#### Heroku - -1. Create `Procfile`: -``` -web: node src/server.js -``` - -2. Deploy: -```bash -heroku create your-app-name -heroku config:set SESSION_SECRET=your-secret-here -git push heroku main -``` - -#### DigitalOcean App Platform - -1. Connect your repository -2. Set environment variables in the dashboard -3. Deploy with one click - -#### AWS EC2 - -Follow "Traditional Server" steps above on an EC2 instance. - -## Security Hardening - -### 1. Firewall Configuration - -```bash -# UFW (Ubuntu) -sudo ufw allow 22/tcp -sudo ufw allow 80/tcp -sudo ufw allow 443/tcp -sudo ufw enable -``` - -### 2. Environment Variables - -Never commit `.env` file. Use secure methods to transfer: -```bash -# On server -touch .env -chmod 600 .env -nano .env # Add your configuration -``` - -### 3. Database Permissions - -```bash -chmod 700 data/ -chmod 600 data/meshtastic.db -``` - -### 4. Regular Updates - -```bash -# Update system packages -sudo apt update && sudo apt upgrade -y - -# Update Node.js dependencies -npm audit fix -npm update -``` - -### 5. Monitoring - -Install monitoring tools: -```bash -# PM2 for process management -npm install -g pm2 -pm2 start src/server.js --name meshtastic-dashboard -pm2 startup -pm2 save -``` - -## Backup Strategy - -### Automated Backup Script - -Create `/opt/backup-meshtastic.sh`: - -```bash -#!/bin/bash -BACKUP_DIR="/opt/backups/meshtastic" -DATE=$(date +%Y%m%d_%H%M%S) - -mkdir -p $BACKUP_DIR - -# Backup database -cp /opt/meshtastic-dashboard/data/meshtastic.db $BACKUP_DIR/db_$DATE.db - -# Backup configuration -cp /opt/meshtastic-dashboard/.env $BACKUP_DIR/env_$DATE.txt - -# Keep only last 7 days -find $BACKUP_DIR -name "db_*.db" -mtime +7 -delete -find $BACKUP_DIR -name "env_*.txt" -mtime +7 -delete - -echo "Backup completed: $DATE" -``` - -Add to crontab: -```bash -chmod +x /opt/backup-meshtastic.sh -crontab -e -# Add: 0 3 * * * /opt/backup-meshtastic.sh -``` - -## Monitoring and Logs - -### View Logs - -```bash -# Systemd logs -sudo journalctl -u meshtastic-dashboard -f - -# Application logs -tail -f /opt/meshtastic-dashboard/logs/combined.log -tail -f /opt/meshtastic-dashboard/logs/error.log -``` - -### Log Rotation - -Create `/etc/logrotate.d/meshtastic-dashboard`: - -``` -/opt/meshtastic-dashboard/logs/*.log { - daily - rotate 14 - compress - delaycompress - notifempty - missingok - copytruncate -} -``` - -## Performance Optimization - -### 1. Enable Node.js Clustering - -For multi-core servers, modify `src/server.js` to use the cluster module. - -### 2. Database Optimization - -```bash -# Vacuum database periodically -sqlite3 data/meshtastic.db "VACUUM;" -``` - -### 3. Nginx Caching - -Add to nginx configuration: -```nginx -location /css/ { - expires 1y; - add_header Cache-Control "public, immutable"; -} - -location /js/ { - expires 1y; - add_header Cache-Control "public, immutable"; -} -``` - -## Troubleshooting - -### Application Won't Start - -1. Check logs: `sudo journalctl -u meshtastic-dashboard -n 50` -2. Verify Node.js version: `node --version` -3. Check port availability: `sudo netstat -tlnp | grep 3000` -4. Verify permissions on data directory - -### MQTT Connection Issues - -1. Check broker is accessible: `telnet mqtt.meshtastic.org 1883` -2. Verify credentials in `.env` -3. Check firewall rules - -### Database Errors - -1. Check disk space: `df -h` -2. Verify database permissions: `ls -la data/` -3. Try rebuilding: `rm data/meshtastic.db && npm start` - -## Scaling Considerations - -For high traffic deployments: - -1. **Load Balancing**: Use multiple instances behind nginx -2. **Database**: Consider PostgreSQL for better concurrency -3. **Caching**: Add Redis for session storage -4. **CDN**: Use CloudFlare for static assets -5. **Monitoring**: Add Prometheus + Grafana - -## Maintenance - -### Weekly Tasks -- Review error logs -- Check disk space -- Verify backups - -### Monthly Tasks -- Update dependencies -- Review security advisories -- Optimize database -- Rotate API keys if used - -### Quarterly Tasks -- Review access logs -- Update SSL certificates (if not using auto-renewal) -- Performance audit -- Security audit - -## Support - -For deployment issues: -1. Check logs first -2. Review documentation -3. Check GitHub issues -4. Contact maintainers - ---- - -**Last Updated**: 2025 -**Maintainer**: Meshtastic Community diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md deleted file mode 100644 index 915a144..0000000 --- a/PROJECT_SUMMARY.md +++ /dev/null @@ -1,281 +0,0 @@ -# Meshtastic MQTT Dashboard - Project Summary - -## Overview - -A complete, production-ready Node.js web application for monitoring and managing Meshtastic mesh networks via MQTT. This is a fully self-contained solution similar to the Home Assistant integration. - -## What Was Built - -### Backend (Node.js + Express) - -#### Core Server (`src/server.js`) -- Express.js web server -- Session management with express-session -- Security middleware (Helmet, CORS, rate limiting) -- Graceful shutdown handling -- Error handling middleware - -#### Authentication System (`src/auth/`) -- User registration and login with bcrypt password hashing -- Session-based authentication -- Activity logging for security auditing -- User creation script for initial setup - -#### Database Layer (`src/database/`) -- SQLite database with better-sqlite3 -- Comprehensive schema for: - - Users and authentication - - Meshtastic nodes - - GPS positions - - Text messages - - Device telemetry - - Activity logs -- Prepared statements for performance and security -- Automatic database initialization - -#### MQTT Client (`src/mqtt/`) -- Automatic connection to Meshtastic MQTT broker -- Real-time message processing -- Support for multiple message types: - - Text messages - - Position updates - - Node information - - Telemetry data -- Message publishing for sending to mesh -- Automatic reconnection handling - -#### REST API (`src/routes/api.js`) -Complete API with endpoints for: -- Authentication (login/logout) -- Node management (list all, get details) -- Position tracking (latest positions, history) -- Message history (view and send) -- Telemetry data -- Statistics dashboard -- Data purging - -#### Background Services (`src/services/`) -- Cron service for scheduled tasks -- Automatic data purging based on retention policy -- Configurable schedule (default: daily at 2 AM) - -#### Logging System (`src/utils/`) -- Winston-based structured logging -- Multiple log files (combined, errors) -- Log rotation -- Configurable log levels - -#### Configuration (`src/config/`) -- Environment variable based configuration -- Secure defaults -- Easy customization for different deployments - -### Frontend (Vanilla JavaScript) - -#### Modern Web Interface (`public/`) -- **Responsive Design**: Works on desktop, tablet, and mobile -- **Clean UI**: Modern styling with CSS variables for easy theming -- **No Framework Dependencies**: Pure JavaScript for simplicity - -#### Features: -1. **Login Screen** - - Secure authentication - - Form validation - - Error handling - -2. **Dashboard Overview** - - Real-time statistics cards - - Recent message feed - - MQTT connection status indicator - -3. **Interactive Map** - - Leaflet.js integration - - GPS position tracking - - Clickable markers with node info - - Auto-fit to show all nodes - -4. **Message Management** - - Send messages to mesh network - - View message history with metadata - - Channel selection - - Signal quality indicators (SNR, RSSI) - -5. **Node Monitoring** - - Grid view of all nodes - - Online/offline status - - Detailed node information modal - - Battery levels, signal strength - - Hardware information - -6. **Settings Panel** - - Manual data purging - - Configurable retention periods - - Application information - -### Security Features - -- Password hashing with bcrypt (10 rounds) -- Session-based authentication -- HTTP security headers (Helmet) -- Rate limiting on API endpoints -- CSRF protection ready -- Activity logging for audit trails -- Secure session cookies - -### Database Schema - -**Tables Created:** -1. `users` - User accounts -2. `nodes` - Meshtastic node information -3. `positions` - GPS location history -4. `messages` - Text message history -5. `telemetry` - Device telemetry data -6. `activity_log` - User activity tracking - -**Indexes Created:** -- Node ID lookups -- Position timestamps -- Message timestamps and senders -- Telemetry tracking - -### Configuration Options - -All configurable via `.env`: -- Server port -- MQTT broker settings -- Session secrets -- Data retention policies -- Cron schedules -- Log levels -- Rate limiting - -## File Structure - -``` -meshtastic-mqtt-dashboard/ -├── src/ -│ ├── auth/ -│ │ └── auth.js # Authentication logic -│ ├── config/ -│ │ └── config.js # Configuration management -│ ├── database/ -│ │ ├── db.js # Database initialization -│ │ └── queries.js # Prepared statements -│ ├── mqtt/ -│ │ └── client.js # MQTT client implementation -│ ├── routes/ -│ │ └── api.js # REST API endpoints -│ ├── scripts/ -│ │ └── createUser.js # User creation utility -│ ├── services/ -│ │ └── cron.js # Background job scheduler -│ ├── utils/ -│ │ └── logger.js # Logging utility -│ └── server.js # Main application entry -├── public/ -│ ├── css/ -│ │ └── style.css # Modern UI styles -│ ├── js/ -│ │ └── app.js # Frontend application -│ └── index.html # Single page app -├── .env.example # Environment template -├── .gitignore # Git ignore rules -├── package.json # Dependencies -├── README.md # Full documentation -├── QUICKSTART.md # Quick start guide -└── PROJECT_SUMMARY.md # This file -``` - -## Technology Stack - -### Backend -- **Node.js**: Runtime environment -- **Express.js**: Web framework -- **better-sqlite3**: Fast SQLite database -- **mqtt**: MQTT client library -- **bcryptjs**: Password hashing -- **express-session**: Session management -- **winston**: Logging -- **node-cron**: Job scheduling -- **helmet**: Security headers -- **express-rate-limit**: Rate limiting - -### Frontend -- **Vanilla JavaScript**: No frameworks, pure JS -- **Leaflet.js**: Interactive maps -- **OpenStreetMap**: Map tiles -- **CSS3**: Modern styling with variables -- **Fetch API**: HTTP requests - -### Database -- **SQLite**: Embedded database -- **WAL mode**: Better concurrency - -## Key Features Implemented - -✅ MQTT automatic connection and message storage -✅ SQLite database with comprehensive schema -✅ User authentication with bcrypt -✅ Session management -✅ REST API for all operations -✅ Interactive map with GPS tracking -✅ Message viewing and sending -✅ Node monitoring and metadata -✅ Automatic data purging with cron -✅ Manual data purging button -✅ Activity logging -✅ Comprehensive error handling -✅ Security best practices -✅ Modern, responsive UI -✅ Real-time statistics -✅ Configurable via environment variables -✅ Complete documentation - -## Getting Started - -1. Install dependencies: `npm install` -2. Copy environment file: `cp .env.example .env` -3. Edit `.env` with your settings -4. Create user: `npm run create-user` -5. Start server: `npm start` -6. Open browser: `http://localhost:3000` - -See `QUICKSTART.md` for detailed instructions. - -## Production Ready - -This application is production-ready with: -- Proper error handling -- Security best practices -- Logging and monitoring -- Graceful shutdown -- Session management -- Rate limiting -- Input validation -- SQL injection protection (prepared statements) -- XSS protection -- CSRF ready - -## Future Enhancement Ideas - -- WebSocket support for real-time updates -- User management interface -- Email notifications -- Export data to CSV/JSON -- Advanced filtering and search -- Grafana integration -- Multi-user permissions -- Custom alerts and triggers -- Mobile app (PWA) - -## License - -MIT License - Free to use, modify, and distribute - -## Support - -See README.md for troubleshooting and detailed documentation. - ---- - -**Built with ❤️ for the Meshtastic community** diff --git a/QUICKSTART.md b/QUICKSTART.md deleted file mode 100644 index cfc261d..0000000 --- a/QUICKSTART.md +++ /dev/null @@ -1,107 +0,0 @@ -# Quick Start Guide - -Get your Meshtastic MQTT Dashboard up and running in 5 minutes! - -## Step 1: Install Dependencies - -```bash -npm install -``` - -## Step 2: Configure Environment - -```bash -cp .env.example .env -``` - -Edit the `.env` file and change the `SESSION_SECRET` to a random string: - -```env -SESSION_SECRET=your-random-secret-here-change-this -``` - -Leave other settings as default to connect to the public Meshtastic MQTT broker. - -## Step 3: Create a User Account - -```bash -npm run create-user -``` - -Enter your desired username and password when prompted. - -Example: -``` -Enter username: admin -Enter password: ******** -Confirm password: ******** - -User 'admin' created successfully! -``` - -## Step 4: Start the Application - -```bash -npm start -``` - -You should see: -``` -Connected to MQTT broker -Subscribed to topic: msh/US/# -Server running on http://localhost:3000 -``` - -## Step 5: Access the Dashboard - -Open your web browser and navigate to: - -``` -http://localhost:3000 -``` - -Login with the username and password you created in Step 3. - -## What's Next? - -- **View Messages**: Navigate to the Messages tab to see incoming mesh messages -- **Check the Map**: Click on the Map tab to see node locations -- **Monitor Nodes**: View all discovered nodes in the Nodes tab -- **Send Messages**: Use the message form to send text to the mesh network - -## Troubleshooting - -**Can't see any data?** -- Wait a few minutes for MQTT messages to arrive -- Check that the MQTT status indicator in the header shows as connected (green dot) -- The public Meshtastic network may have variable activity depending on your region - -**Can't login?** -- Make sure you created a user with `npm run create-user` -- Check that the SESSION_SECRET is set in your `.env` file -- Try clearing your browser cookies - -**Port 3000 already in use?** -- Change the PORT in your `.env` file to another port (e.g., 3001) -- Restart the application - -## Default Configuration - -The default configuration connects to: -- **MQTT Broker**: mqtt.meshtastic.org (public) -- **Topic**: msh/US/# (all US channels) -- **Port**: 3000 -- **Data Retention**: 30 days - -To change these, edit your `.env` file. - -## Production Deployment - -For production use: -1. Change `NODE_ENV=production` in `.env` -2. Use a strong, random `SESSION_SECRET` -3. Set up HTTPS with a reverse proxy (nginx, Caddy, etc.) -4. Configure firewall rules -5. Set up automatic backups of the `data/` directory - -Enjoy your Meshtastic dashboard! diff --git a/README.md b/README.md index 58c2fd3..316230f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# Meshtastic MQTT Dashboard +# Meshcore Dashboard (USB-based) -A complete, self-contained Node.js web application for monitoring and managing Meshtastic networks via MQTT. Features real-time message tracking, GPS location mapping, node management, and automatic data retention with secure authentication. +A complete, self-contained Node.js web application for monitoring and managing a Meshcore client device via USB. Features real-time message tracking, GPS location mapping, and automatic data retention with secure authentication. -![Dashboard Preview](https://via.placeholder.com/800x400?text=Meshtastic+Dashboard) +![Dashboard Screenshot](screenshot-1.png) +![Messaging Screenshot](screenshot-2.png) ## Features -- **Real-time MQTT Integration** - Automatically connects to Meshtastic MQTT broker and stores all messages +- **Real-time USB Serial Integration** - Automatically connects to Meshcore USB serial device and stores all messages - **Interactive Map View** - View GPS locations of all nodes on an interactive Leaflet map - **Message Management** - View message history and send messages to the mesh network - **Node Monitoring** - Track all nodes with detailed metadata including battery levels, signal strength, and telemetry @@ -16,42 +17,18 @@ A complete, self-contained Node.js web application for monitoring and managing M - **SQLite Database** - All data stored locally in a SQLite database - **Comprehensive Logging** - Winston-based logging for debugging and monitoring -## Architecture - -``` -meshtastic-mqtt-dashboard/ -├── src/ -│ ├── auth/ # Authentication and authorization -│ ├── config/ # Application configuration -│ ├── database/ # SQLite database setup and queries -│ ├── mqtt/ # MQTT client and message handlers -│ ├── routes/ # Express API routes -│ ├── scripts/ # Utility scripts (user creation, etc.) -│ ├── services/ # Background services (cron jobs) -│ ├── utils/ # Utilities (logging, etc.) -│ └── server.js # Main application entry point -├── public/ -│ ├── css/ # Stylesheets -│ ├── js/ # Frontend JavaScript -│ └── index.html # Main HTML file -├── data/ # SQLite database (auto-created) -├── logs/ # Application logs (auto-created) -├── .env # Environment configuration -└── package.json # Dependencies -``` - ## Prerequisites - Node.js 16.x or higher - npm or yarn -- Access to a Meshtastic MQTT broker (default: mqtt.meshtastic.org) +- Access to a Meshcore device via USB-Serial ## Installation 1. **Clone or navigate to the project directory:** ```bash -cd meshtastic-mqtt-dashboard +cd meshcore-usb-dashboard ``` 2. **Install dependencies:** @@ -68,32 +45,7 @@ Copy the example environment file and edit it: cp .env.example .env ``` -Edit `.env` with your settings: - -```env -# Server Configuration -PORT=3000 -NODE_ENV=production - -# Session Secret (CHANGE THIS!) -SESSION_SECRET=your-random-secret-string-here - -# MQTT Configuration -MQTT_BROKER=mqtt://mqtt.meshtastic.org -MQTT_PORT=1883 -MQTT_USERNAME=meshdev -MQTT_PASSWORD=large4cats -MQTT_TOPIC=msh/US/# - -# Data Retention (in days) -DATA_RETENTION_DAYS=30 - -# Cron schedule for automatic data purging (daily at 2 AM) -PURGE_CRON_SCHEDULE=0 2 * * * - -# Logging -LOG_LEVEL=info -``` +Edit `.env` with your settings, especially the name of your serial port (i.e. COM10) 4. **Create your first user:** @@ -124,22 +76,6 @@ http://localhost:3000 Login with the username and password you created. -## Configuration - -### MQTT Settings - -The application connects to the Meshtastic MQTT broker to receive messages. Configure these settings in `.env`: - -- **MQTT_BROKER**: The MQTT broker URL (default: mqtt://mqtt.meshtastic.org) -- **MQTT_USERNAME**: MQTT username (default: meshdev) -- **MQTT_PASSWORD**: MQTT password (default: large4cats) -- **MQTT_TOPIC**: MQTT topic to subscribe to (default: msh/US/# for all US channels) - -To monitor a specific region, change the topic: -- `msh/US/#` - All US channels -- `msh/EU/#` - All EU channels -- `msh/US/2/json/#` - Specific channel - ### Data Retention Configure automatic data purging: @@ -149,98 +85,9 @@ Configure automatic data purging: You can also manually purge data from the Settings tab in the web interface. -### Security +## Database -**IMPORTANT**: Change the `SESSION_SECRET` in your `.env` file to a random string for production use. - -## Usage - -### Dashboard Overview - -The dashboard provides several tabs: - -1. **Overview** - Statistics and recent messages at a glance -2. **Map** - Interactive map showing GPS locations of all nodes -3. **Messages** - Send and view message history -4. **Nodes** - View all discovered nodes and their metadata -5. **Settings** - Data management and application settings - -### Sending Messages - -1. Navigate to the **Messages** tab -2. Type your message in the text field -3. Choose a device to send **From** -4. Click **Send** - -Messages will be broadcast to the mesh network via MQTT. - -### Viewing Node Details - -1. Navigate to the **Nodes** tab -2. Click on any node card to view detailed information -3. Modal will display: - - Node ID and names - - Hardware model and firmware version - - Battery level and voltage - - Signal metrics - - Last heard timestamp - -### Map View - -The **Map** tab displays GPS locations of all nodes: - -- Click on markers to see node details -- Map automatically fits to show all nodes -- Click **Refresh Map** to update positions - -### Data Management - -From the **Settings** tab: - -1. Select retention period (7, 14, 30, 60, or 90 days) -2. Click **Purge Old Data** -3. Confirm the action - -This will delete messages, positions, and telemetry older than the selected period. - -## API Endpoints - -The application provides a REST API for programmatic access: - -### Authentication -- `POST /api/login` - Login with username/password -- `POST /api/logout` - Logout current session -- `GET /api/auth/status` - Check authentication status - -### Data Access -- `GET /api/nodes` - Get all nodes -- `GET /api/nodes/:nodeId` - Get specific node details -- `GET /api/positions` - Get latest positions for all nodes -- `GET /api/positions/:nodeId` - Get position history for a node -- `GET /api/messages` - Get recent messages -- `GET /api/messages/node/:nodeId` - Get messages for a specific node -- `GET /api/telemetry/:nodeId` - Get telemetry history for a node -- `GET /api/stats` - Get dashboard statistics - -### Actions -- `POST /api/messages/send` - Send a message to the mesh -- `POST /api/purge` - Purge old data - -### Status -- `GET /api/mqtt/status` - Get MQTT connection status - -All endpoints (except login) require authentication. - -## Database Schema - -The application uses SQLite with the following tables: - -- **users** - User accounts for authentication -- **nodes** - Meshtastic node information -- **positions** - GPS position updates -- **messages** - Text messages -- **telemetry** - Device telemetry data -- **activity_log** - User activity logging +The application uses SQLite. Database file location: `data/meshtastic.db` @@ -253,81 +100,15 @@ Logs are stored in the `logs/` directory: Log level can be configured with `LOG_LEVEL` in `.env` (debug, info, warn, error). -## Troubleshooting - -### Cannot connect to MQTT broker - -1. Check your internet connection -2. Verify MQTT broker URL in `.env` -3. Check username/password if using a private broker -4. Review logs in `logs/error.log` - -### No data appearing - -1. Verify MQTT connection (check status indicator in header) -2. Ensure MQTT topic is correct for your region -3. Check that there is active mesh traffic on the topic -4. Review logs for any error messages - -### Messages sent over MQTT aren't sent over the radio - -1. Ensure the sending radio (node) has a channel called "mqtt" -2. Ensure the node has Uplink and Downlink enabled on both the default channel (LongFast, etc) and the mqtt channel -3. Ensure the node's setting under LoRa is "Ok to MQTT" - -### Database errors - -1. Ensure the `data/` directory is writable -2. Check disk space -3. Try deleting `data/meshtastic.db` and restarting (this will delete all data) - -### Login issues - -1. Verify user was created successfully with `npm run create-user` -2. Check SESSION_SECRET is set in `.env` -3. Clear browser cookies and try again - -## T-Deck - -- It's touch screen, which is often easier than using the trackball. -- To pair over Bluetooth, power on the T-Deck and LONG PRESS (about 2 seconds) the Meshtastic logo. -- To set the timezone properly, it should be `PST8PDT,M3.2.0,M11.1.0` for PST -- To get map tiles: https://www.jeffgeerling.com/blog/2025/adding-gps-and-grid-maps-my-meshtastic-t-deck - -## Development - -### Project Structure - -- **Backend**: Express.js server with REST API -- **Frontend**: Vanilla JavaScript (no frameworks) -- **Database**: SQLite with better-sqlite3 -- **MQTT**: mqtt.js client -- **Map**: Leaflet.js for interactive maps -- **Logging**: Winston for structured logging -- **Security**: bcrypt for password hashing, express-session for sessions - -### Adding Features - -1. **New API endpoint**: Add route to `src/routes/api.js` -2. **New database table**: Modify `src/database/db.js` and `src/database/queries.js` -3. **MQTT message handler**: Update `src/mqtt/client.js` -4. **Frontend UI**: Modify `public/index.html`, `public/css/style.css`, and `public/js/app.js` - ## Contributing -Contributions are welcome! Please ensure: - -1. Code follows existing style conventions -2. All features are properly documented -3. Security best practices are followed -4. No sensitive data in commits +Contributions are welcome! ## Security Considerations - Change default `SESSION_SECRET` in production - Use HTTPS in production (configure reverse proxy) - Regularly update dependencies -- Review and limit access to MQTT credentials - Use strong passwords for user accounts - Enable firewall rules to restrict access @@ -341,12 +122,12 @@ For issues and feature requests, please open an issue on the project repository. ## Acknowledgments -- Meshtastic project for the excellent mesh networking platform +- Meshcore project - OpenStreetMap for map tiles - All contributors and testers --- **Version**: 1.0.0 -**Author**: Meshtastic Community -**Last Updated**: 2025 +**Author**: zyphlar +**Last Updated**: 2026 diff --git a/STRUCTURE.txt b/STRUCTURE.txt deleted file mode 100644 index 99a90b7..0000000 --- a/STRUCTURE.txt +++ /dev/null @@ -1,55 +0,0 @@ -meshtastic-mqtt-dashboard/ -│ -├── src/ # Backend source code -│ ├── auth/ -│ │ └── auth.js # User authentication & authorization -│ │ -│ ├── config/ -│ │ └── config.js # Application configuration -│ │ -│ ├── database/ -│ │ ├── db.js # SQLite database setup -│ │ └── queries.js # Database query definitions -│ │ -│ ├── mqtt/ -│ │ └── client.js # MQTT client & message handlers -│ │ -│ ├── routes/ -│ │ └── api.js # REST API endpoints -│ │ -│ ├── scripts/ -│ │ └── createUser.js # CLI tool to create users -│ │ -│ ├── services/ -│ │ └── cron.js # Background job scheduler -│ │ -│ ├── utils/ -│ │ └── logger.js # Winston logging utility -│ │ -│ └── server.js # Main application entry point -│ -├── public/ # Frontend static files -│ ├── css/ -│ │ └── style.css # Modern UI stylesheet -│ │ -│ ├── js/ -│ │ └── app.js # Frontend JavaScript application -│ │ -│ └── index.html # Single Page Application HTML -│ -├── data/ # Database storage (auto-created) -│ └── meshtastic.db # SQLite database file -│ -├── logs/ # Application logs (auto-created) -│ ├── combined.log # All logs -│ └── error.log # Error logs only -│ -├── .env # Environment configuration (create from .env.example) -├── .env.example # Environment template -├── .gitignore # Git ignore rules -├── package.json # NPM dependencies & scripts -├── README.md # Complete documentation -├── QUICKSTART.md # Quick start guide -├── PROJECT_SUMMARY.md # Project overview -└── STRUCTURE.txt # This file - directory structure - diff --git a/check-data.js b/check-data.js deleted file mode 100644 index 47c93fd..0000000 --- a/check-data.js +++ /dev/null @@ -1,49 +0,0 @@ -// Check database for received data -const db = require('./src/database/db'); -const { statsQueries, messageQueries, nodeQueries, positionQueries } = require('./src/database/queries'); - -console.log('\n=== Database Statistics ===\n'); - -try { - const stats = { - messages: statsQueries.getMessageCount.get(), - nodes: statsQueries.getNodeCount.get(), - positions: statsQueries.getPositionCount.get(), - dbSize: statsQueries.getDbSize() - }; - - console.log(`Messages: ${stats.messages.count}`); - console.log(`Nodes: ${stats.nodes.count}`); - console.log(`Positions: ${stats.positions.count}`); - console.log(`DB Size: ${(stats.dbSize / 1024 / 1024).toFixed(2)} MB`); - - if (stats.messages.count > 0) { - console.log('\n=== Recent Messages ===\n'); - const messages = messageQueries.getRecentMessages.all(5); - messages.forEach(msg => { - console.log(`[${msg.created_at}] ${msg.from_node}: ${msg.text || '(no text)'}`); - }); - } - - if (stats.nodes.count > 0) { - console.log('\n=== Nodes ===\n'); - const nodes = nodeQueries.getAllNodes.all(); - nodes.forEach(node => { - console.log(`${node.node_id} - ${node.long_name || node.short_name || 'Unknown'} (${node.hardware_model || 'N/A'})`); - }); - } - - if (stats.positions.count > 0) { - console.log('\n=== Recent Positions ===\n'); - const positions = positionQueries.getLatestPositions.all(); - positions.slice(0, 5).forEach(pos => { - console.log(`${pos.node_id}: ${pos.latitude}, ${pos.longitude} @ ${pos.timestamp}`); - }); - } - - console.log('\n'); - process.exit(0); -} catch (error) { - console.error('Error:', error.message); - process.exit(1); -} diff --git a/package.json b/package.json index 22daf1a..da5a392 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,32 @@ { - "name": "meshtastic-mqtt-dashboard", + "name": "meshcore-usb-dashboard", "version": "1.0.0", - "description": "Complete Meshtastic MQTT Dashboard with web frontend", + "description": "MeshCore USB Serial Dashboard with web frontend", + "type": "module", "main": "src/server.js", "scripts": { "start": "node src/server.js", "dev": "nodemon src/server.js", - "init-db": "node src/database/init.js", "create-user": "node src/scripts/createUser.js" }, "keywords": [ - "meshtastic", - "mqtt", + "meshcore", + "serial", "dashboard", "iot" ], "author": "", "license": "MIT", "dependencies": { + "@liamcottle/meshcore.js": "latest", "express": "^4.18.2", "express-session": "^1.17.3", "bcryptjs": "^2.4.3", - "mqtt": "^5.3.4", "better-sqlite3": "^9.2.2", "dotenv": "^16.3.1", "winston": "^3.11.0", "node-cron": "^3.0.3", - "protobufjs": "^7.2.5", + "serialport": "^12.0.0", "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "cors": "^2.8.5" diff --git a/public/css/style.css b/public/css/style.css index 9560cce..0c7dba7 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -28,14 +28,8 @@ body { line-height: 1.6; } -/* Screen Management */ -.screen { - min-height: 100vh; -} - -.hidden { - display: none !important; -} +.screen { min-height: 100vh; } +.hidden { display: none !important; } /* Login Screen */ .login-container { @@ -43,7 +37,7 @@ body { justify-content: center; align-items: center; min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%); } .login-box { @@ -55,18 +49,8 @@ body { max-width: 400px; } -.login-box h1 { - font-size: 2rem; - margin-bottom: 0.5rem; - text-align: center; - color: var(--text-primary); -} - -.subtitle { - text-align: center; - color: var(--text-secondary); - margin-bottom: 2rem; -} +.login-box h1 { font-size: 2rem; margin-bottom: 0.5rem; text-align: center; } +.subtitle { text-align: center; color: var(--text-secondary); margin-bottom: 2rem; } /* Header */ .header { @@ -79,9 +63,18 @@ body { box-shadow: var(--shadow); } -.header h1 { - font-size: 1.5rem; - color: var(--text-primary); +.header-left { display: flex; align-items: center; gap: 1rem; } +.header h1 { font-size: 1.5rem; color: var(--text-primary); } + +.device-badge { + background: #eff6ff; + color: #1d4ed8; + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.8rem; + font-weight: 600; + font-family: monospace; + border: 1px solid #bfdbfe; } .header-right { @@ -90,7 +83,7 @@ body { gap: 1.5rem; } -.mqtt-status { +.serial-status { display: flex; align-items: center; gap: 0.5rem; @@ -105,14 +98,9 @@ body { background-color: var(--secondary-color); } -.status-dot.connected { - background-color: var(--success-color); -} +.status-dot.connected { background-color: var(--success-color); } -.user-info { - color: var(--text-secondary); - font-size: 0.875rem; -} +.user-info { color: var(--text-secondary); font-size: 0.875rem; } .data-last-received { display: flex; @@ -122,11 +110,9 @@ body { color: var(--text-secondary); } -.data-label { - opacity: 0.7; -} +.data-label { opacity: 0.7; } -/* Navigation Tabs */ +/* Navigation */ .nav-tabs { background: var(--surface-color); border-bottom: 1px solid var(--border-color); @@ -147,29 +133,13 @@ body { transition: all 0.2s; } -.nav-tab:hover { - color: var(--primary-color); -} +.nav-tab:hover { color: var(--primary-color); } +.nav-tab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); } -.nav-tab.active { - color: var(--primary-color); - border-bottom-color: var(--primary-color); -} - -/* Content Area */ -.content { - padding: 2rem; - max-width: 1400px; - margin: 0 auto; -} - -.tab-content { - display: none; -} - -.tab-content.active { - display: block; -} +/* Content */ +.content { padding: 2rem; max-width: 1400px; margin: 0 auto; } +.tab-content { display: none; } +.tab-content.active { display: block; } /* Stats Grid */ .stats-grid { @@ -189,20 +159,9 @@ body { gap: 1rem; } -.stat-icon { - font-size: 2.5rem; -} - -.stat-value { - font-size: 2rem; - font-weight: 700; - color: var(--text-primary); -} - -.stat-label { - font-size: 0.875rem; - color: var(--text-secondary); -} +.stat-icon { font-size: 2.5rem; } +.stat-value { font-size: 2rem; font-weight: 700; color: var(--text-primary); } +.stat-label { font-size: 0.875rem; color: var(--text-secondary); } /* Panel */ .panel { @@ -213,34 +172,30 @@ body { margin-bottom: 1.5rem; } -.panel h2 { - font-size: 1.25rem; - margin-bottom: 1rem; - color: var(--text-primary); +.panel h2 { font-size: 1.25rem; margin-bottom: 1rem; color: var(--text-primary); } +.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; } +.panel-header h2 { margin: 0; } + +/* Device Info */ +.device-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1rem; } -.panel-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; +.device-info-item { + background: var(--bg-color); + padding: 0.75rem 1rem; + border-radius: 0.375rem; + border: 1px solid var(--border-color); } -.panel-header h2 { - margin: 0; -} +.device-info-label { font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 0.25rem; } +.device-info-value { font-size: 0.95rem; font-family: monospace; color: var(--text-primary); font-weight: 600; } /* Forms */ -.form-group { - margin-bottom: 1.5rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - color: var(--text-primary); - font-weight: 500; -} +.form-group { margin-bottom: 1.5rem; } +.form-group label { display: block; margin-bottom: 0.5rem; color: var(--text-primary); font-weight: 500; } .form-group input, .form-group select { @@ -258,19 +213,14 @@ body { border-color: var(--primary-color); } -.form-row { - display: flex; - gap: 0.5rem; - align-items: center; -} +.form-row { display: flex; gap: 0.5rem; align-items: center; } +.form-row input { flex: 1; padding: 0.75rem; border: 1px solid var(--border-color); border-radius: 0.375rem; font-size: 1rem; } +.form-row input:focus { outline: none; border-color: var(--primary-color); } +.form-row select { min-width: 140px; padding: 0.75rem; border: 1px solid var(--border-color); border-radius: 0.375rem; font-size: 0.9rem; } -.form-row input { - flex: 1; -} - -.form-row select { - min-width: 120px; -} +.radio-group { display: flex; gap: 1.5rem; } +.radio-label { display: flex; align-items: center; gap: 0.5rem; cursor: pointer; font-weight: 500; color: var(--text-primary); } +.radio-label input[type="radio"] { width: auto; } /* Buttons */ .btn { @@ -281,75 +231,30 @@ body { font-weight: 500; cursor: pointer; transition: all 0.2s; + white-space: nowrap; } -.btn-primary { - background-color: var(--primary-color); - color: white; -} - -.btn-primary:hover { - background-color: var(--primary-hover); -} - -.btn-secondary { - background-color: var(--secondary-color); - color: white; -} - -.btn-secondary:hover { - background-color: #475569; -} - -.btn-danger { - background-color: var(--danger-color); - color: white; -} - -.btn-danger:hover { - background-color: #dc2626; -} +.btn-primary { background-color: var(--primary-color); color: white; } +.btn-primary:hover { background-color: var(--primary-hover); } +.btn-secondary { background-color: var(--secondary-color); color: white; } +.btn-secondary:hover { background-color: #475569; } +.btn-danger { background-color: var(--danger-color); color: white; } +.btn-danger:hover { background-color: #dc2626; } +.btn:disabled { opacity: 0.5; cursor: not-allowed; } /* Messages */ -.message-list { - max-height: 600px; - overflow-y: auto; -} +.message-list { max-height: 600px; overflow-y: auto; } -.message-item { - padding: 1rem; - border-bottom: 1px solid var(--border-color); -} +.message-item { padding: 1rem; border-bottom: 1px solid var(--border-color); } +.message-item:last-child { border-bottom: none; } +.message-item.outbound { background: #f0f9ff; } -.message-item:last-child { - border-bottom: none; -} +.message-header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; align-items: center; } +.message-from { font-weight: 600; color: var(--text-primary); } +.message-time { font-size: 0.875rem; color: var(--text-secondary); } +.message-text { color: var(--text-primary); } -.message-header { - display: flex; - justify-content: space-between; - margin-bottom: 0.5rem; -} - -.message-from { - font-weight: 600; - color: var(--text-primary); -} - -.message-time { - font-size: 0.875rem; - color: var(--text-secondary); -} - -.message-text { - color: var(--text-primary); -} - -.message-meta { - margin-top: 0.5rem; - font-size: 0.75rem; - color: var(--text-secondary); -} +.message-meta { margin-top: 0.5rem; font-size: 0.75rem; color: var(--text-secondary); display: flex; gap: 0.75rem; flex-wrap: wrap; } .message-channel { display: inline-block; @@ -361,7 +266,19 @@ body { text-transform: uppercase; } -/* Nodes Grid */ +.message-badge { + display: inline-block; + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.7rem; + font-weight: 500; +} + +.badge-direct { background: #eff6ff; color: #1d4ed8; } +.badge-channel { background: #f0fdf4; color: #166534; } +.badge-delivered { background: #d1fae5; color: #065f46; } + +/* Contacts Grid */ .nodes-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); @@ -377,28 +294,12 @@ body { transition: all 0.2s; } -.node-card:hover { - box-shadow: var(--shadow); - transform: translateY(-2px); -} +.node-card:hover { box-shadow: var(--shadow); transform: translateY(-2px); } +.node-card.favourite { border-color: #fbbf24; background: #fffbeb; } -.node-card-header { - display: flex; - justify-content: space-between; - align-items: start; - margin-bottom: 0.75rem; -} - -.node-name { - font-weight: 600; - color: var(--text-primary); -} - -.node-id { - font-size: 0.75rem; - color: var(--text-secondary); - font-family: monospace; -} +.node-card-header { display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem; } +.node-name { font-weight: 600; color: var(--text-primary); } +.node-id { font-size: 0.75rem; color: var(--text-secondary); font-family: monospace; } .node-badge { padding: 0.25rem 0.5rem; @@ -407,212 +308,125 @@ body { font-weight: 500; } -.node-badge.online { - background-color: #d1fae5; - color: #065f46; -} +.node-badge.online { background-color: #d1fae5; color: #065f46; } +.node-badge.offline { background-color: #fee2e2; color: #991b1b; } +.node-badge.type-chat { background: #eff6ff; color: #1d4ed8; } +.node-badge.type-repeater { background: #f0fdf4; color: #166534; } +.node-badge.type-room { background: #fdf4ff; color: #7e22ce; } +.node-badge.type-sensor { background: #fff7ed; color: #9a3412; } -.node-badge.offline { - background-color: #fee2e2; - color: #991b1b; -} +.node-info { font-size: 0.875rem; color: var(--text-secondary); } +.node-info-row { display: flex; justify-content: space-between; margin-bottom: 0.25rem; } -.node-info { - font-size: 0.875rem; - color: var(--text-secondary); -} - -.node-info-row { - display: flex; - justify-content: space-between; - margin-bottom: 0.25rem; -} +/* Ports List */ +.ports-list { margin-top: 1rem; } +.port-item { display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border-color); font-family: monospace; font-size: 0.9rem; } /* Map */ -.map-container { - height: 600px; - border-radius: 0.5rem; - overflow: hidden; - margin-bottom: 1rem; -} - -.map-controls { - display: flex; - gap: 0.5rem; -} +.map-container { height: 600px; border-radius: 0.5rem; overflow: hidden; margin-bottom: 1rem; } +.map-controls { display: flex; gap: 0.5rem; } /* Modal */ .modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; + position: fixed; top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } .modal-content { - background: var(--surface-color); - padding: 2rem; - border-radius: 0.5rem; - max-width: 600px; - width: 90%; - max-height: 80vh; - overflow-y: auto; - position: relative; + background: var(--surface-color); padding: 2rem; border-radius: 0.5rem; + max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; position: relative; } -.modal-close { - position: absolute; - top: 1rem; - right: 1rem; - font-size: 1.5rem; - cursor: pointer; - color: var(--text-secondary); -} +.modal-close { position: absolute; top: 1rem; right: 1rem; font-size: 1.5rem; cursor: pointer; color: var(--text-secondary); } +.modal-close:hover { color: var(--text-primary); } +.node-detail { margin-top: 1rem; } -.modal-close:hover { - color: var(--text-primary); -} +.detail-row { display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--border-color); } +.detail-label { font-weight: 500; color: var(--text-secondary); } +.detail-value { color: var(--text-primary); font-family: monospace; word-break: break-all; max-width: 60%; text-align: right; } -.node-detail { - margin-top: 1rem; -} - -.detail-row { - display: flex; - justify-content: space-between; - padding: 0.75rem 0; - border-bottom: 1px solid var(--border-color); -} - -.detail-label { - font-weight: 500; - color: var(--text-secondary); -} - -.detail-value { - color: var(--text-primary); - font-family: monospace; -} - -/* Error Messages */ -.error-message { - color: var(--danger-color); - font-size: 0.875rem; - margin-top: 0.5rem; - margin-bottom: 1rem; -} - -/* Success Messages */ -.success-message { - color: var(--success-color); - font-size: 0.875rem; - margin-top: 0.5rem; - margin-bottom: 1rem; -} +/* Error / Success */ +.error-message { color: var(--danger-color); font-size: 0.875rem; margin-top: 0.5rem; margin-bottom: 1rem; } +.success-message { color: var(--success-color); font-size: 0.875rem; margin-top: 0.5rem; } /* Empty State */ -.empty-state { - text-align: center; - padding: 3rem; - color: var(--text-secondary); +.empty-state { text-align: center; padding: 3rem; color: var(--text-secondary); } +.empty-state-icon { font-size: 3rem; margin-bottom: 1rem; } + +/* Notification */ +.notification { + position: fixed; top: 1rem; right: 1rem; padding: 1rem 1.5rem; + border-radius: 0.5rem; box-shadow: var(--shadow-lg); z-index: 2000; + max-width: 350px; animation: slideIn 0.3s ease; } -.empty-state-icon { - font-size: 3rem; - margin-bottom: 1rem; +.notification.success { background: #d1fae5; color: #065f46; border-left: 4px solid var(--success-color); } +.notification.warning { background: #fef3c7; color: #92400e; border-left: 4px solid var(--warning-color); } +.notification.error { background: #fee2e2; color: #991b1b; border-left: 4px solid var(--danger-color); } +.notification.info { background: #eff6ff; color: #1e40af; border-left: 4px solid var(--primary-color); } + +@keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } } -/* Responsive Design */ +/* Responsive */ @media (max-width: 768px) { - .header { - flex-direction: column; - gap: 1rem; - align-items: flex-start; - } - - .header-right { - width: 100%; - justify-content: space-between; - } - - .nav-tabs { - overflow-x: auto; - padding: 0 1rem; - } - - .content { - padding: 1rem; - } - - .stats-grid { - grid-template-columns: 1fr; - } - - .form-row { - flex-direction: column; - } - - .form-row select { - width: 100%; - } - - .nodes-grid { - grid-template-columns: 1fr; - } + .header { flex-direction: column; gap: 1rem; align-items: flex-start; } + .header-right { width: 100%; justify-content: space-between; } + .nav-tabs { overflow-x: auto; padding: 0 1rem; } + .content { padding: 1rem; } + .stats-grid { grid-template-columns: 1fr; } + .form-row { flex-direction: column; } + .form-row select { width: 100%; } + .nodes-grid { grid-template-columns: 1fr; } } -/* Scrollbar Styling */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} +/* Scrollbar */ +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-track { background: var(--bg-color); } +::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 4px; } +::-webkit-scrollbar-thumb:hover { background: var(--secondary-color); } -::-webkit-scrollbar-track { - background: var(--bg-color); -} - -::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--secondary-color); -} - -/* Loading Indicator */ +/* Loading */ .loading { - display: inline-block; - width: 1rem; - height: 1rem; - border: 2px solid var(--border-color); - border-top-color: var(--primary-color); - border-radius: 50%; - animation: spin 0.8s linear infinite; + display: inline-block; width: 1rem; height: 1rem; + border: 2px solid var(--border-color); border-top-color: var(--primary-color); + border-radius: 50%; animation: spin 0.8s linear infinite; } -@keyframes spin { - to { transform: rotate(360deg); } -} +@keyframes spin { to { transform: rotate(360deg); } } -/* Map marker pulse animation for newly heard nodes */ @keyframes markerPulse { - 0% { - transform: scale(1); - box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 0 rgba(255, 255, 255, 0.7); - } - 50% { - transform: scale(1.6); - box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 10px rgba(255, 255, 255, 0); - } - 100% { - transform: scale(1); - box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 0 rgba(255, 255, 255, 0); - } + 0% { transform: scale(1); box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 0 rgba(255,255,255,0.7); } + 50% { transform: scale(1.6); box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 10px rgba(255,255,255,0); } + 100% { transform: scale(1); box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 0 rgba(255,255,255,0); } } + +.settings-form { max-width: 400px; } + +/* Message filter tabs */ +.msg-filter-tabs { + display: flex; + gap: 0.25rem; + background: var(--bg-color); + padding: 0.25rem; + border-radius: 0.375rem; + border: 1px solid var(--border-color); +} + +.msg-filter-btn { + padding: 0.375rem 0.75rem; + border: none; + background: transparent; + border-radius: 0.25rem; + font-size: 0.8rem; + font-weight: 500; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; +} + +.msg-filter-btn:hover { color: var(--primary-color); } +.msg-filter-btn.active { background: var(--surface-color); color: var(--primary-color); box-shadow: var(--shadow); } diff --git a/public/index.html b/public/index.html index 5198370..2302944 100644 --- a/public/index.html +++ b/public/index.html @@ -3,7 +3,7 @@ - Meshtastic MQTT Dashboard + MeshCore Dashboard @@ -12,8 +12,8 @@