init tools repo

This commit is contained in:
2025-11-23 19:57:05 +01:00
commit d778206940
35 changed files with 6197 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# Server Configuration
PORT=3001
# Database Configuration
DB_PATH=./devbench.db
# Session Configuration
SESSION_SECRET=your-secret-key-here
# SSH Configuration (for provision script)
SSH_USER=asf
SSH_HOST=asf-tb.duckdns.org
SSH_PASS=ASF
# Provision Script Configuration
PROVISION_SCRIPT=./provision_vm.sh

View File

@@ -0,0 +1,52 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Database
*.db
*.sqlite
*.sqlite3
data/
# Logs
logs/
*.log
/var/log/
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Docker
.dockerignore
# Temporary files
tmp/
temp/

View File

@@ -0,0 +1,121 @@
# Changelog - DevBench Manager Updates
## Changes Made
### 1. Updated SSH Access Configuration
- **File**: `provision_vm.sh`
- **Changes**:
- Updated SSH connection to use port 49152
- Changed SSH host to `asf@asf-server.duckdns.org -p 49152`
- Modified SSH command to include port parameter
### 2. Updated Output Parsing
- **File**: `provision_vm.sh`
- **Changes**:
- Now extracts SSH Port and VNC Port from script output
- Removed IP address extraction
- Parses output format: `SSH Port: XXXX` and `VNC Port: XXXX`
- Extracts VM name from success message
### 3. Updated Server-Side Processing
- **File**: `server.js`
- **Changes**:
- Modified DevBench creation to parse new output format (SSH_PORT, VNC_PORT, VM_NAME)
- Stores only port numbers in database (ssh_info and vnc_info fields)
- Removed vm_ip field from database schema
- Added `/help` route for help page
### 4. Updated User Interface
- **File**: `views/dashboard.ejs`
- **Changes**:
- Display "SSH Port" instead of full SSH command
- Display "VNC Port" instead of full VNC info
- Added link to help page in connection info section
- Improved visual presentation with larger font for ports
### 5. Updated Admin Interface
- **File**: `views/admin.ejs`
- **Changes**:
- Replaced "IP Address" column with "SSH Port" and "VNC Port" columns
- Shows port numbers for each DevBench
### 6. Added Help Page
- **File**: `views/help.ejs` (NEW)
- **Features**:
- Step-by-step guide for using SSH Config Manager tool
- Instructions for configuring SSH access
- Download link for SSH Config Manager
- Connection information and important notes
- Styled with Bootstrap cards and custom CSS
### 7. Updated Navigation
- **File**: `views/layout.ejs`
- **Changes**:
- Added Help icon/link in navbar
- Added TBM icon (tbm-icon.png) in navbar
- Added favicon using TBM icon
- Help link accessible from all pages
### 8. Added Downloadable Tool
- **Location**: `public/downloads/db_vm_ssh_config_manager.exe`
- **Purpose**: SSH configuration management tool
- **Access**: Available at `/downloads/db_vm_ssh_config_manager.exe`
### 9. Added TBM Icon
- **Location**: `public/images/tbm-icon.png`
- **Usage**:
- Favicon for all pages
- Icon in navbar next to logo
- Branding element
### 10. Updated Documentation
- **File**: `README.md`
- **Changes**:
- Added SSH Configuration section
- Updated database schema documentation
- Added help page to user features
- Updated project structure
- Added help route to API endpoints
## Summary of User-Facing Changes
### For Users:
1. **Simplified Connection Info**: Now shows only SSH Port and VNC Port numbers
2. **Help Page**: Accessible via navbar, provides detailed setup instructions
3. **SSH Config Tool**: Downloadable tool to simplify SSH configuration
4. **Visual Improvements**: TBM icon added to branding
### For Administrators:
1. **Updated Admin Dashboard**: Shows SSH and VNC ports instead of IP addresses
2. **Same management capabilities**: All admin functions remain unchanged
## Technical Details
### SSH Connection Format:
- **Old**: Full SSH command string
- **New**: Port number only (e.g., "6004")
### VNC Connection Format:
- **Old**: Full VNC connection string
- **New**: Port number only (e.g., "5004")
### Database Changes:
- Removed: `vm_ip` field
- Modified: `ssh_info` now stores SSH port number
- Modified: `vnc_info` now stores VNC port number
## Testing Recommendations
1. Test DevBench creation with new output parsing
2. Verify SSH port and VNC port display correctly
3. Test help page accessibility
4. Verify SSH Config Manager download
5. Check TBM icon display on all pages
6. Test on different browsers for favicon display
## Migration Notes
If you have existing DevBenches in the database:
- Old entries may still have full SSH/VNC strings or IP addresses
- New entries will have port numbers only
- Consider running a migration script if needed to update old entries

View File

@@ -0,0 +1,27 @@
FROM node:18-alpine
# Install bash and other dependencies
RUN apk add --no-cache bash sshpass openssh-client wget
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy application files
COPY . .
# Ensure public directory permissions
RUN chmod -R 755 /app/public
# Create directories for database and logs
RUN mkdir -p /app/data /app/logs
# Expose port
EXPOSE 3001
# Start the application
CMD ["npm", "start"]

View File

@@ -0,0 +1,248 @@
# 🚀 DevBench Manager - Quick Start Guide
## One-Minute Setup
```bash
# 1. Clone and enter directory
git clone <repository-url>
cd ASF_devbench
# 2. Deploy
chmod +x deploy.sh
./deploy.sh
# 3. Access
# Open: http://localhost:9090
# Login: admin / admin123
```
## That's It! 🎉
---
## What You Get
### 🌐 Web Interface
- **URL**: http://localhost:9090
- **Caddy**: https://tbm.nabd-co.com (if configured)
### 👤 Default Login
- **Username**: `admin`
- **Password**: `admin123`
- ⚠️ Change this after first login!
### ✨ Features
- ✅ Create and manage VMs
- ✅ Real-time log streaming
- ✅ SSH/VNC connection info
- ✅ Dark/Light theme toggle
- ✅ Help page with guides
- ✅ SSH Config Manager download
---
## Quick Commands
### Check Status
```bash
docker ps | grep devbench-manager
```
### View Logs
```bash
docker-compose logs -f
```
### Health Check
```bash
curl http://localhost:9090/health
```
### Stop
```bash
docker-compose down
```
### Restart
```bash
docker-compose restart
```
---
## First Steps
### 1. Login
- Open http://localhost:9090
- Enter: admin / admin123
### 2. Change Password
- Go to Admin Panel
- Click "Reset Password"
- Enter new secure password
### 3. Add Users
- Click "Add User"
- Enter username (letters only)
- Enter email and password
### 4. Create DevBench
- User logs in
- Click "Create DevBench"
- Enter name (alphanumeric, hyphens, underscores)
- Watch real-time creation logs
### 5. Access VM
- Copy SSH Port from connection info
- Download SSH Config Manager from Help page
- Follow setup guide
---
## Theme Toggle
Click the floating button (bottom-right) to switch between:
- 🌞 Light Theme
- 🌙 Dark Theme
Your preference is saved automatically!
---
## Need Help?
### In-App
- Click the **Help** icon in navbar
- Download SSH Config Manager
- Follow step-by-step guide
### Documentation
- `README.md` - Overview
- `docs/ARCHITECTURE.md` - System design
- `docs/STRUCTURE.md` - Code structure
- `docs/DEPLOYMENT.md` - Deployment guide
- `docs/API.md` - API reference
### Troubleshooting
```bash
# Container not running?
docker-compose logs
# Port in use?
sudo lsof -i :9090
# Permission issues?
sudo chown -R $USER:$USER data logs
# Network issues?
docker network inspect caddy_network
```
---
## Common Tasks
### Add a User
1. Login as admin
2. Go to Admin Panel
3. Click "Add User"
4. Fill in details
5. Click "Add User"
### Create a DevBench
1. Login as user
2. Click "Create DevBench"
3. Enter name
4. Watch creation progress
5. Copy connection info
### Access VM via SSH
1. Note SSH Port from DevBench
2. Download SSH Config Manager
3. Add VM to config
4. Use: `ssh vm-name`
### Check VM Status
1. Go to Dashboard
2. Click "Check Status"
3. View current status
---
## Configuration
### Change Port
Edit `docker-compose.yml`:
```yaml
ports:
- "8080:3001" # Change 9090 to 8080
```
### Environment Variables
Create `.env`:
```bash
NODE_ENV=production
SECRET_KEY=your-secret-key
ADMIN_PASSWORD=your-password
```
### Caddy Proxy
Add to Caddyfile:
```
tbm.nabd-co.com {
reverse_proxy devbench-manager:3001
}
```
---
## Maintenance
### Backup Database
```bash
cp data/devbench.db backups/devbench-$(date +%Y%m%d).db
```
### View Logs
```bash
docker-compose logs --tail=100
```
### Update Application
```bash
git pull
./deploy.sh
```
### Clean Up
```bash
./cleanup.sh
```
---
## Support
📧 **Email**: admin@nabd-co.com
📚 **Docs**: `/docs` directory
🐛 **Issues**: Check logs first
---
## Quick Reference
| Task | Command |
|------|---------|
| Deploy | `./deploy.sh` |
| Start | `docker-compose up -d` |
| Stop | `docker-compose down` |
| Logs | `docker-compose logs -f` |
| Health | `curl http://localhost:9090/health` |
| Backup | `cp data/devbench.db backups/` |
| Clean | `./cleanup.sh` |
---
**Ready to go!** 🚀
For detailed information, see `README.md` or `/docs` directory.

View File

@@ -0,0 +1,456 @@
# DevBench Manager 🚀
A modern web application for managing DevBench virtual machines with user authentication, real-time monitoring, and dark theme support.
![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)
![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)
![Docker](https://img.shields.io/badge/docker-required-blue.svg)
## 📋 Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Quick Start](#quick-start)
- [Documentation](#documentation)
- [Project Structure](#project-structure)
- [Configuration](#configuration)
- [Usage](#usage)
- [API](#api)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## 🎯 Overview
DevBench Manager provides a centralized web interface for creating, managing, and accessing virtual machine development environments. Built with Node.js and Express, it offers real-time monitoring, WebSocket-based updates, and a modern responsive UI with dark theme support.
## ✨ Features
### 👤 User Features
- 🔐 Secure authentication with session management
- 🖥️ Create and manage personal DevBenches
- 📊 Real-time status monitoring
- 📝 Live log streaming during VM creation
- 🔌 Easy access to SSH and VNC connection info
- 📚 Comprehensive help page with setup guide
- 📥 Download SSH Config Manager tool
- 🌓 Dark/Light theme toggle
- 📱 Responsive design for mobile and desktop
### 👨‍💼 Admin Features
- 👥 User management (add, delete, reset passwords)
- 🗂️ View all users and their DevBenches
- 📈 System-wide DevBench overview
- 🔧 Centralized management dashboard
- 📊 User activity monitoring
### 🛠️ Technical Features
- ⚡ Real-time WebSocket updates
- 🔒 Secure password hashing (bcrypt)
- 💾 SQLite database for persistence
- 🐳 Docker containerization
- 🔄 Automatic status checks (60-second interval)
- 🎨 Modern Bootstrap 5 UI
- 🌐 Caddy reverse proxy support
- 📡 SSH-based VM provisioning
- 🔍 Health check endpoint
- 📋 Comprehensive logging
## 🚀 Quick Start
### Prerequisites
- **Docker**: Version 20.10+ ([Install Docker](https://docs.docker.com/get-docker/))
- **Docker Compose**: Version 2.0+ ([Install Compose](https://docs.docker.com/compose/install/))
- **Git**: For cloning the repository
- **SSH Access**: To VM host (asf-server.duckdns.org:49152)
### One-Command Deployment
```bash
# Clone the repository
git clone <repository-url>
cd ASF_devbench
# Deploy
chmod +x deploy.sh
./deploy.sh
```
That's it! 🎉
### Access the Application
- **Direct Access**: http://localhost:9090
- **Via Caddy Proxy**: https://tbm.nabd-co.com
### Default Credentials
```
Username: admin
Password: admin123
```
⚠️ **Important**: Change the default password after first login!
### Alternative: Manual Installation
For development without Docker:
```bash
# Install dependencies
npm install
# Start application
npm start
# Or with auto-reload
npm run dev
```
Access at: http://localhost:3001
## Configuration
### Caddy Proxy Configuration
Add this to your Caddyfile:
```
tbm.nabd-co.com {
reverse_proxy devbench-manager:3001
}
```
Make sure your Caddy container is on the same `caddy_network`. If you need to create the network:
```bash
docker network create caddy_network
```
### Default Admin Account
- Username: `admin`
- Password: `admin123`
- Email: `admin@nabd-co.com`
**Important:** Change the default admin password after first login!
## Usage
### For Administrators
1. Login with admin credentials
2. Add users through the "Add User" button
3. Monitor all DevBenches from the admin dashboard
4. Manage users (reset passwords, delete users)
### For Users
1. Login with provided credentials
2. Create DevBenches using the "Create DevBench" button
3. Monitor DevBench status and connection information
4. Activate or delete DevBenches as needed
### DevBench Naming Rules
- DevBench names can only contain letters, numbers, hyphens (-), and underscores (_)
- Final DevBench name format: `username_devbenchname`
- Example: User "john" creates "test-db" → Final name: "john_test-db"
### SSH Configuration
The application includes a downloadable SSH Config Manager tool (`db_vm_ssh_config_manager.exe`) that helps users configure SSH access to their VMs. The tool:
- Configures SSH jump host (asf-jump)
- Sets up VM-specific SSH configurations
- Generates easy-to-use SSH commands
- Available at `/downloads/db_vm_ssh_config_manager.exe`
Access the help page at `/help` for detailed instructions on using the SSH Config Manager.
## API Endpoints
### Authentication
- `GET /login` - Login page
- `POST /login` - Login submission
- `GET /logout` - Logout
### Admin Routes
- `GET /admin` - Admin dashboard
- `POST /admin/add-user` - Add new user
- `POST /admin/delete-user/:id` - Delete user
- `POST /admin/reset-password/:id` - Reset user password
### User Routes
- `GET /dashboard` - User dashboard
- `GET /help` - Help page with SSH configuration guide
- `POST /create-devbench` - Create new DevBench
- `POST /delete-devbench/:id` - Delete DevBench
- `POST /activate-devbench/:id` - Activate DevBench
- `GET /check-status/:id` - Check DevBench status
## Database Schema
### Users Table
- `id` - Primary key
- `username` - Unique username
- `email` - User email
- `password` - Hashed password
- `is_admin` - Admin flag
- `created_at` - Creation timestamp
### DevBenches Table
- `id` - Primary key
- `user_id` - Foreign key to users
- `name` - DevBench name (user input)
- `actual_name` - Actual VM name from script
- `status` - Current status (active/inactive/creating)
- `ssh_info` - SSH port number
- `vnc_info` - VNC port number
- `created_at` - Creation timestamp
- `updated_at` - Last update timestamp
## Security Features
- Password hashing with bcryptjs
- Session-based authentication
- Input validation and sanitization
- Admin-only routes protection
- SQL injection prevention with parameterized queries
## Monitoring
- Automatic status checking every minute
- Real-time WebSocket updates
- Live log streaming during DevBench creation
- Connection information extraction and display
## Troubleshooting
### Common Issues
1. **Cannot access via Caddy**:
- Ensure `caddy_network` exists: `docker network create caddy_network`
- Check if Caddy container is on the same network
- Verify Caddyfile configuration points to `devbench-manager:3001`
2. **Script timeout**: DevBench creation takes up to 30 minutes
3. **SSH connection issues**: Ensure sshpass is installed and SSH credentials are correct
4. **Permission issues**: Make sure the provision script is executable
5. **Database issues**: Check SQLite file permissions
6. **Container networking**:
- Check networks: `docker network ls`
- Inspect container: `docker inspect devbench-manager`
- Check if both containers are on caddy_network
### Logs
Application logs are available in the container or local environment where the app is running.
## 📚 Documentation
Comprehensive documentation is available in the `/docs` directory:
- **[Architecture](docs/ARCHITECTURE.md)**: System architecture, components, and data flow
- **[Structure](docs/STRUCTURE.md)**: Project structure and file descriptions
- **[Deployment](docs/DEPLOYMENT.md)**: Detailed deployment guide and troubleshooting
- **[API](docs/API.md)**: Complete API reference and examples
## 📁 Project Structure
```
ASF_devbench/
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # System architecture
│ ├── STRUCTURE.md # Project structure
│ ├── DEPLOYMENT.md # Deployment guide
│ └── API.md # API documentation
├── public/ # Static assets
│ ├── css/ # Stylesheets (with dark theme)
│ ├── images/ # Logos and icons
│ └── downloads/ # SSH Config Manager tool
├── views/ # EJS templates
│ ├── layout.ejs # Base layout
│ ├── login.ejs # Login page
│ ├── dashboard.ejs # User dashboard
│ ├── admin.ejs # Admin panel
│ └── help.ejs # Help page
├── data/ # Database (created on deploy)
├── logs/ # Application logs
├── server.js # Main application
├── config.js # Configuration
├── provision_vm.sh # VM provisioning script
├── deploy.sh # Deployment script
├── docker-compose.yml # Container orchestration
├── Dockerfile # Container definition
└── README.md # This file
```
## 🎨 Theme Support
DevBench Manager includes a beautiful dark theme:
- **Toggle**: Click the theme button (bottom-right corner)
- **Persistence**: Theme preference saved in browser
- **Smooth Transitions**: Animated theme switching
- **Full Coverage**: All pages and components themed
## 🔧 Configuration
### Environment Variables
Create a `.env` file:
```bash
NODE_ENV=production
PORT=3001
SECRET_KEY=your-secure-secret-key
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=your-secure-password
```
### Docker Configuration
Edit `docker-compose.yml` to customize:
```yaml
ports:
- "9090:3001" # Change external port
environment:
- NODE_ENV=production
- SECRET_KEY=${SECRET_KEY}
volumes:
- ./data:/app/data
- ./logs:/app/logs
```
### Caddy Reverse Proxy
Add to your Caddyfile:
```
tbm.nabd-co.com {
reverse_proxy devbench-manager:3001
}
```
## 📖 Usage
### For Users
1. **Login**: Use provided credentials
2. **Create DevBench**: Click "Create DevBench" button
3. **Monitor Progress**: Watch real-time log output
4. **Access VM**: Use displayed SSH/VNC ports
5. **Get Help**: Click help icon for setup guide
### For Administrators
1. **Add Users**: Click "Add User" in admin panel
2. **Manage DevBenches**: View and delete any DevBench
3. **Reset Passwords**: Reset user passwords as needed
4. **Monitor System**: View all users and their activity
### SSH Configuration
Download the SSH Config Manager tool from the help page to easily configure SSH access to your VMs.
## 🔌 API
### Health Check
```bash
curl http://localhost:9090/health
```
### Create DevBench
```bash
curl -X POST http://localhost:9090/create-devbench \
-H "Content-Type: application/json" \
-d '{"name":"my-vm"}'
```
See [API Documentation](docs/API.md) for complete reference.
## 🐛 Troubleshooting
### Container Won't Start
```bash
# Check logs
docker-compose logs
# Verify port availability
sudo lsof -i :9090
# Check permissions
sudo chown -R $USER:$USER data logs
```
### Cannot Access Web Interface
```bash
# Verify container is running
docker ps | grep devbench-manager
# Test health endpoint
curl http://localhost:9090/health
# Check firewall
sudo ufw allow 9090/tcp
```
### SSH Connection Fails
```bash
# Test SSH manually
ssh -p 49152 asf@asf-server.duckdns.org
# Check from container
docker exec devbench-manager ./provision_vm.sh status test_vm
```
See [Deployment Guide](docs/DEPLOYMENT.md) for more troubleshooting steps.
## 🔄 Updating
```bash
# Pull latest changes
git pull origin main
# Backup database
cp data/devbench.db data/devbench.db.backup
# Redeploy
./deploy.sh
```
## 🤝 Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
## 📝 License
MIT License - see LICENSE file for details
## 🙏 Acknowledgments
- Built with [Express.js](https://expressjs.com/)
- UI powered by [Bootstrap 5](https://getbootstrap.com/)
- Icons from [Font Awesome](https://fontawesome.com/)
- Reverse proxy by [Caddy](https://caddyserver.com/)
## 📞 Support
For issues, questions, or contributions:
- 📧 Email: admin@nabd-co.com
- 📚 Documentation: `/docs` directory
- 🐛 Issues: GitHub Issues (if applicable)
---
Made with ❤️ by NABD Solutions

View File

@@ -0,0 +1,462 @@
# DevBench Manager - Final Updates Summary
## 🎉 Completed Updates
### 1. ✅ Dark Theme Implementation
**Added Files:**
- Enhanced `public/css/style.css` with comprehensive dark theme styles
**Modified Files:**
- `views/layout.ejs`: Added theme toggle button and JavaScript
**Features:**
- 🌓 Floating theme toggle button (bottom-right)
- 💾 Theme preference saved in localStorage
- 🎨 Complete dark theme for all components
- ✨ Smooth transitions between themes
- 🌙 Moon/Sun icon toggle
**Usage:**
- Click the floating button in bottom-right corner
- Theme preference persists across sessions
- Works on all pages (dashboard, admin, help, login)
---
### 2. ✅ Script Cleanup
**Removed Scripts:**
-`check-network.sh` (debug script)
-`debug-deploy.sh` (debug script)
-`deploy.sh` (old version)
-`test-script.sh` (debug script)
-`update-ui.sh` (debug script)
-`fix-deployment.sh` (debug script)
-`DEPLOYMENT.md` (old, replaced with docs/)
**Renamed Scripts:**
-`deploy-final.sh``deploy.sh` (simplified name)
**Kept Scripts:**
-`deploy.sh` - Main deployment script
-`install.sh` - Local installation
-`start.sh` - Start application
-`cleanup.sh` - Container cleanup
-`provision_vm.sh` - VM provisioning
**Result:**
- Clean, organized project structure
- Only essential scripts remain
- Clear purpose for each script
---
### 3. ✅ Comprehensive Documentation
**Created `/docs` Folder:**
#### `docs/ARCHITECTURE.md`
- 📐 System architecture diagrams
- 🏗️ Component descriptions
- 🔄 Data flow diagrams
- 🔒 Security architecture
- 📊 Technology stack
- 🚀 Deployment architecture
- ⚡ Performance considerations
- 🛠️ Maintenance guidelines
#### `docs/STRUCTURE.md`
- 📁 Complete directory tree
- 📄 File descriptions
- 🔗 Data flow through files
- ⚙️ Configuration hierarchy
- 🌐 Network architecture
- 🔐 File permissions
- 🏗️ Build process
- ⚡ Runtime process
#### `docs/DEPLOYMENT.md`
- 📋 Prerequisites checklist
- 🚀 Quick start guide
- 📝 Detailed deployment steps
- ⚙️ Configuration options
- ✅ Verification procedures
- 🐛 Troubleshooting guide
- 🔧 Maintenance tasks
- 💾 Backup & recovery
- 🔒 Security hardening
- 📊 Monitoring setup
#### `docs/API.md`
- 🔌 Complete API reference
- 📡 WebSocket documentation
- 🔐 Authentication details
- 📊 Data models
- 💡 Usage examples
- 🔒 Security considerations
- 📝 Error responses
- 🚀 Rate limiting (future)
**Documentation Features:**
- Clear, structured format
- Code examples included
- Diagrams and visual aids
- Troubleshooting sections
- Best practices
- Security guidelines
---
### 4. ✅ Enhanced README.md
**New Sections:**
- 🎯 Overview with badges
- 📋 Table of contents
- ✨ Feature highlights with emojis
- 🚀 Quick start guide
- 📚 Documentation links
- 📁 Project structure
- 🎨 Theme support
- 🔧 Configuration guide
- 📖 Usage instructions
- 🔌 API quick reference
- 🐛 Troubleshooting
- 🔄 Update instructions
- 🤝 Contributing guidelines
- 📞 Support information
**Improvements:**
- More visual and engaging
- Better organized
- Quick access to information
- Clear deployment steps
- Professional presentation
---
## 📊 Project Statistics
### Files Summary
**Total Files:** 25 core files
**By Category:**
- Documentation: 5 files (README.md + 4 in docs/)
- Source Code: 2 files (server.js, config.js)
- Templates: 5 files (views/*.ejs)
- Static Assets: 6 files (CSS, images, downloads)
- Scripts: 5 files (deploy, install, start, cleanup, provision)
- Configuration: 5 files (package.json, Dockerfile, docker-compose.yml, .env.example, .gitignore)
### Lines of Code
**Estimated:**
- JavaScript: ~1,500 lines
- EJS Templates: ~800 lines
- CSS: ~500 lines
- Shell Scripts: ~400 lines
- Documentation: ~3,000 lines
- **Total: ~6,200 lines**
---
## 🎨 Theme Implementation Details
### Color Scheme
**Light Theme:**
- Background: #ffffff
- Text: #212529
- Primary: #1a365d (NABD Blue)
- Accent: #f39c12 (NABD Orange)
**Dark Theme:**
- Background: #1a1a1a
- Text: #e0e0e0
- Cards: #2d2d2d
- Primary: #1a365d
- Accent: #f39c12
### Components Themed
✅ Navbar
✅ Cards
✅ Tables
✅ Forms
✅ Modals
✅ Buttons
✅ Alerts
✅ Code blocks
✅ Connection info
✅ Help page
✅ Login page
---
## 📁 Final Project Structure
```
ASF_devbench/
├── docs/ ✨ NEW
│ ├── ARCHITECTURE.md ✨ NEW
│ ├── STRUCTURE.md ✨ NEW
│ ├── DEPLOYMENT.md ✨ NEW
│ └── API.md ✨ NEW
├── public/
│ ├── css/
│ │ └── style.css 🔄 UPDATED (dark theme)
│ ├── images/
│ │ ├── nabd-logo.svg
│ │ ├── nabd-logo-white.svg
│ │ └── tbm-icon.png
│ └── downloads/
│ └── db_vm_ssh_config_manager.exe
├── views/
│ ├── layout.ejs 🔄 UPDATED (theme toggle)
│ ├── login.ejs
│ ├── dashboard.ejs
│ ├── admin.ejs
│ └── help.ejs
├── server.js
├── config.js
├── provision_vm.sh
├── deploy.sh 🔄 RENAMED (from deploy-final.sh)
├── install.sh
├── start.sh
├── cleanup.sh
├── docker-compose.yml
├── Dockerfile
├── package.json
├── .env.example
├── .gitignore
├── README.md 🔄 UPDATED (comprehensive)
├── CHANGELOG.md
└── UPDATES_SUMMARY.md ✨ NEW (this file)
```
---
## 🚀 Deployment Instructions
### Quick Deploy
```bash
# 1. Navigate to project
cd ASF_devbench
# 2. Deploy
./deploy.sh
# 3. Access
# http://localhost:9090
# https://tbm.nabd-co.com
```
### What Gets Deployed
✅ Docker container with Node.js app
✅ SQLite database (persistent)
✅ Application logs (persistent)
✅ Provision script (mounted)
✅ Static assets (CSS, images, downloads)
✅ All views and templates
✅ Dark theme support
✅ WebSocket server
✅ Health check endpoint
---
## 🎯 Key Features Summary
### User Experience
- 🌓 Dark/Light theme toggle
- 📱 Responsive design
- ⚡ Real-time updates
- 📊 Live log streaming
- 🔌 Easy connection info
- 📚 Comprehensive help
### Administration
- 👥 User management
- 🗂️ DevBench overview
- 🔧 System monitoring
- 📈 Activity tracking
### Technical
- 🐳 Docker containerized
- 🔒 Secure authentication
- 💾 Persistent storage
- 🌐 Reverse proxy ready
- 📡 WebSocket support
- 🔍 Health monitoring
---
## 📚 Documentation Access
All documentation is in the `/docs` folder:
1. **Architecture**: `docs/ARCHITECTURE.md`
- System design and components
- Data flow and security
2. **Structure**: `docs/STRUCTURE.md`
- File organization
- Code structure
3. **Deployment**: `docs/DEPLOYMENT.md`
- Installation guide
- Troubleshooting
4. **API**: `docs/API.md`
- Endpoint reference
- Examples
---
## ✅ Verification Checklist
### Before Deployment
- [ ] Docker and Docker Compose installed
- [ ] SSH access to VM host verified
- [ ] Port 9090 available
- [ ] Caddy network created (if using proxy)
### After Deployment
- [ ] Container running: `docker ps | grep devbench-manager`
- [ ] Health check: `curl http://localhost:9090/health`
- [ ] Web interface accessible
- [ ] Login works with default credentials
- [ ] Theme toggle works
- [ ] Help page accessible
- [ ] SSH Config Manager downloadable
### Testing
- [ ] Create test DevBench
- [ ] Monitor real-time logs
- [ ] Check connection info
- [ ] Test dark theme
- [ ] Verify admin panel
- [ ] Test user management
---
## 🔄 Migration Notes
### From Previous Version
**No breaking changes!**
All existing data and functionality preserved:
- ✅ Database schema unchanged
- ✅ API endpoints unchanged
- ✅ Authentication unchanged
- ✅ DevBench management unchanged
**New additions:**
- ✨ Dark theme (optional)
- 📚 Documentation (reference)
- 🧹 Cleaner project structure
---
## 🎓 Learning Resources
### For Developers
- Read `docs/ARCHITECTURE.md` for system design
- Read `docs/STRUCTURE.md` for code organization
- Read `docs/API.md` for endpoint details
### For Operators
- Read `docs/DEPLOYMENT.md` for deployment
- Read `README.md` for quick start
- Check `CHANGELOG.md` for updates
### For Users
- Click Help icon in navbar
- Download SSH Config Manager
- Follow step-by-step guide
---
## 🎉 What's New
### Version 1.0.0 (2025-11-23)
**Major Updates:**
1. 🌓 Dark theme support with toggle
2. 📚 Comprehensive documentation (4 guides)
3. 🧹 Cleaned up debug scripts
4. 📖 Enhanced README with quick start
5. 🎨 Improved visual design
6. 📊 Better project organization
**Technical Improvements:**
- Cleaner codebase
- Better documentation
- Easier deployment
- Professional presentation
---
## 🚀 Next Steps
### Immediate
1. Deploy using `./deploy.sh`
2. Change default admin password
3. Add users
4. Create test DevBenches
### Short Term
- Configure Caddy reverse proxy
- Set up SSL/TLS
- Configure backups
- Monitor logs
### Long Term
- Consider PostgreSQL migration
- Implement rate limiting
- Add CSRF protection
- Set up monitoring dashboard
---
## 📞 Support
**Documentation:**
- README.md - Quick start
- docs/ARCHITECTURE.md - System design
- docs/STRUCTURE.md - Code structure
- docs/DEPLOYMENT.md - Deployment guide
- docs/API.md - API reference
**Contact:**
- Email: admin@nabd-co.com
- Help Page: Click help icon in app
---
## 🎊 Conclusion
All requested updates have been successfully implemented:
**Dark Theme**: Fully functional with toggle button
**Script Cleanup**: Removed all debug scripts
**Documentation**: 4 comprehensive guides created
**README**: Enhanced with quick start and structure
**Project Organization**: Clean and professional
The DevBench Manager is now production-ready with:
- Modern UI with theme support
- Comprehensive documentation
- Clean project structure
- Easy deployment process
- Professional presentation
**Ready to deploy!** 🚀
---
*Last Updated: November 23, 2025*
*Version: 1.0.0*

View File

@@ -0,0 +1,23 @@
#!/bin/bash
echo "Cleaning up DevBench Manager containers and volumes..."
# Stop and remove containers
echo "Stopping containers..."
docker-compose down --remove-orphans
# Remove any existing containers with the same name
echo "Removing existing containers..."
docker rm -f devbench-manager 2>/dev/null || true
# Remove dangling containers
echo "Removing dangling containers..."
docker container prune -f
# List and optionally remove volumes
echo "Current volumes:"
docker volume ls | grep devbench
echo ""
echo "Cleanup complete!"
echo "You can now run: docker-compose up -d --build"

View File

@@ -0,0 +1,42 @@
module.exports = {
// Server configuration
port: process.env.PORT || 3001,
// Database configuration
database: {
path: process.env.DB_PATH || './data/devbench.db'
},
// Session configuration
session: {
secret: process.env.SESSION_SECRET || 'devbench-secret-key',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
},
// SSH configuration for provision script
ssh: {
user: process.env.SSH_USER || 'asf',
host: process.env.SSH_HOST || 'asf-tb.duckdns.org',
password: process.env.SSH_PASS || 'ASF'
},
// Provision script configuration
provision: {
scriptPath: process.env.PROVISION_SCRIPT || './provision_vm.sh',
timeout: 30 * 60 * 1000, // 30 minutes
statusCheckInterval: 60 * 1000 // 1 minute
},
// Default admin user
defaultAdmin: {
username: 'admin',
email: process.env.ADMIN_EMAIL || 'admin@nabd-co.com',
password: process.env.ADMIN_PASSWORD || 'admin123'
},
// Validation rules
validation: {
username: /^[a-zA-Z]+$/, // Only letters for admin usernames
devbenchName: /^[a-zA-Z0-9_-]+$/ // Letters, numbers, hyphens, underscores for devbench names
}
};

View File

@@ -0,0 +1,81 @@
#!/bin/bash
echo "🚀 Deploying DevBench Manager with all improvements..."
# Create directories
echo "📁 Creating directories..."
mkdir -p data logs public/css public/images public/downloads
# Make scripts executable
echo "🔧 Setting permissions..."
chmod +x provision_vm.sh *.sh
# Clean up containers
echo "🧹 Cleaning up existing containers..."
docker-compose down --remove-orphans 2>/dev/null || true
docker rm -f devbench-manager 2>/dev/null || true
# Create network
if ! docker network ls | grep -q caddy_network; then
echo "🌐 Creating caddy_network..."
docker network create caddy_network
else
echo "✅ caddy_network already exists"
fi
# Build and deploy
echo "🏗️ Building and starting container..."
docker-compose up -d --build
# Wait for startup
echo "⏳ Waiting for container startup..."
sleep 15
# Check status
if docker ps | grep -q devbench-manager; then
echo ""
echo "🎉 SUCCESS! DevBench Manager is running with latest updates:"
echo ""
echo "✅ Updated SSH access (asf@asf-server.duckdns.org:49152)"
echo "✅ Simplified connection info (SSH Port & VNC Port only)"
echo "✅ Added Help page with SSH Config Manager guide"
echo "✅ Added TBM icon branding"
echo "✅ SSH Config Manager tool available for download"
echo "✅ Enhanced UI with copy-to-clipboard functionality"
echo ""
echo "🌐 Access Points:"
echo " Direct: http://localhost:9090"
echo " Via Caddy: https://tbm.nabd-co.com"
echo ""
echo "🔐 Default Login:"
echo " Username: admin"
echo " Password: admin123"
echo ""
echo "🛠️ Useful Commands:"
echo " Check logs: docker-compose logs -f"
echo " Check health: curl http://localhost:9090/health"
echo " Stop: docker-compose down"
# Test health endpoint
echo ""
echo "🏥 Health Check:"
if curl -s http://localhost:9090/health > /dev/null; then
echo "✅ Application is healthy"
else
echo "⚠️ Health check failed (may need more time)"
fi
else
echo ""
echo "❌ DEPLOYMENT FAILED!"
echo ""
echo "📋 Checking logs:"
docker-compose logs --tail=20
echo ""
echo "🔍 Container status:"
docker ps -a | grep devbench-manager
exit 1
fi
echo ""
echo "🎯 Deployment completed successfully!"

View File

@@ -0,0 +1,33 @@
version: '3.8'
services:
devbench-manager:
build: .
container_name: devbench-manager
restart: unless-stopped
ports:
- "9090:3001"
environment:
- NODE_ENV=production
- PORT=3001
- SECRET_KEY=${SECRET_KEY:-dev-secret-key-change-in-production}
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@nabd-co.com}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
volumes:
- ./data:/app/data
- ./logs:/app/logs
- ./provision_vm.sh:/app/provision_vm.sh:ro
healthcheck:
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health" ]
interval: 30s
timeout: 10s
retries: 3
networks:
- app-network
- caddy_network
networks:
app-network:
driver: bridge
caddy_network:
external: true

View File

@@ -0,0 +1,649 @@
# DevBench Manager - API Documentation
## Base URL
```
http://localhost:9090
https://tbm.nabd-co.com
```
## Authentication
All API endpoints (except `/login` and `/health`) require authentication via session cookies.
### Session Cookie
- **Name**: `connect.sid`
- **Type**: HTTP-only
- **Secure**: true (in production with HTTPS)
- **SameSite**: Lax
## Endpoints
### Health Check
#### GET /health
Check application health status.
**Authentication**: Not required
**Response**:
```json
{
"status": "ok",
"timestamp": "2025-11-23T12:00:00.000Z",
"version": "1.0.0"
}
```
**Status Codes**:
- `200`: Application is healthy
---
### Authentication
#### GET /login
Display login page.
**Authentication**: Not required
**Response**: HTML page
---
#### POST /login
Authenticate user and create session.
**Authentication**: Not required
**Request Body**:
```json
{
"username": "admin",
"password": "admin123"
}
```
**Response**: Redirect to `/dashboard` or `/admin`
**Status Codes**:
- `302`: Success, redirect to dashboard
- `200`: Failed, re-render login with error
---
#### GET /logout
Destroy session and logout user.
**Authentication**: Required
**Response**: Redirect to `/login`
**Status Codes**:
- `302`: Success, redirect to login
---
### User Dashboard
#### GET /dashboard
Display user's DevBench dashboard.
**Authentication**: Required (User role)
**Response**: HTML page with user's DevBenches
**Status Codes**:
- `200`: Success
- `302`: Redirect to `/admin` if user is admin
- `302`: Redirect to `/login` if not authenticated
---
#### GET /help
Display help page with SSH configuration guide.
**Authentication**: Required
**Response**: HTML page with documentation
**Status Codes**:
- `200`: Success
- `302`: Redirect to `/login` if not authenticated
---
### DevBench Management
#### POST /create-devbench
Create a new DevBench.
**Authentication**: Required (User role)
**Request Body**:
```json
{
"name": "test-vm"
}
```
**Validation**:
- Name must match pattern: `[a-zA-Z0-9_-]+`
- Name must be unique for user
**Response**:
```json
{
"success": true,
"devbenchId": 123
}
```
**Error Response**:
```json
{
"error": "DevBench with this name already exists"
}
```
**Status Codes**:
- `200`: Success
- `400`: Validation error
- `500`: Database error
**Side Effects**:
- Creates database record with status "creating"
- Spawns provision script execution
- Sends real-time updates via WebSocket
---
#### POST /delete-devbench/:id
Delete a DevBench.
**Authentication**: Required (User role, owner only)
**Parameters**:
- `id`: DevBench ID (integer)
**Response**:
```json
{
"success": true
}
```
**Error Response**:
```json
{
"error": "DevBench not found"
}
```
**Status Codes**:
- `200`: Success
- `404`: DevBench not found
- `500`: Database error
**Side Effects**:
- Executes delete command on remote host
- Removes database record
---
#### POST /activate-devbench/:id
Activate a DevBench.
**Authentication**: Required (User role, owner only)
**Parameters**:
- `id`: DevBench ID (integer)
**Response**:
```json
{
"success": true
}
```
**Error Response**:
```json
{
"error": "DevBench not found"
}
```
**Status Codes**:
- `200`: Success
- `404`: DevBench not found
**Side Effects**:
- Executes activate command on remote host
- Updates status via WebSocket
---
#### GET /check-status/:id
Check DevBench status.
**Authentication**: Required (User role, owner only)
**Parameters**:
- `id`: DevBench ID (integer)
**Response**:
```json
{
"status": "active"
}
```
**Possible Status Values**:
- `active`: VM is running
- `inactive`: VM is stopped
- `creating`: VM is being created
- `error`: VM creation failed
- `unknown`: Status cannot be determined
**Status Codes**:
- `200`: Success
**Side Effects**:
- Updates database with current status
---
### Admin Panel
#### GET /admin
Display admin dashboard.
**Authentication**: Required (Admin role)
**Response**: HTML page with users and DevBenches
**Status Codes**:
- `200`: Success
- `403`: Access denied (not admin)
- `302`: Redirect to `/login` if not authenticated
---
#### POST /admin/add-user
Create a new user.
**Authentication**: Required (Admin role)
**Request Body**:
```json
{
"username": "john",
"email": "john@example.com",
"password": "password123"
}
```
**Validation**:
- Username must match pattern: `[a-zA-Z]+` (letters only)
- Username must be unique
- Email must be valid format
- Password required
**Response**:
```json
{
"success": true
}
```
**Error Response**:
```json
{
"error": "Username already exists"
}
```
**Status Codes**:
- `200`: Success
- `400`: Validation error
- `500`: Database error
---
#### POST /admin/delete-user/:id
Delete a user and all their DevBenches.
**Authentication**: Required (Admin role)
**Parameters**:
- `id`: User ID (integer)
**Response**:
```json
{
"success": true
}
```
**Error Response**:
```json
{
"error": "Database error"
}
```
**Status Codes**:
- `200`: Success
- `500`: Database error
**Side Effects**:
- Deletes all user's DevBenches
- Deletes user record
- Cannot delete admin users
---
#### POST /admin/reset-password/:id
Reset user password.
**Authentication**: Required (Admin role)
**Parameters**:
- `id`: User ID (integer)
**Request Body**:
```json
{
"newPassword": "newpassword123"
}
```
**Response**:
```json
{
"success": true
}
```
**Error Response**:
```json
{
"error": "Database error"
}
```
**Status Codes**:
- `200`: Success
- `500`: Database error
**Side Effects**:
- Updates user password (hashed with bcrypt)
- Cannot reset admin passwords
---
### User Info
#### GET /api/user-info
Get current user information for WebSocket registration.
**Authentication**: Required
**Response**:
```json
{
"userId": 1,
"username": "admin",
"isAdmin": true
}
```
**Status Codes**:
- `200`: Success
- `302`: Redirect to `/login` if not authenticated
---
## WebSocket API
### Connection
```javascript
const ws = new WebSocket('ws://localhost:9090');
```
### Registration
After connection, register with user ID:
```javascript
ws.send(JSON.stringify({
type: 'register',
userId: 1
}));
```
### Message Types
#### script_output
Real-time output from provision script.
```json
{
"type": "script_output",
"devbenchId": 123,
"data": "Cloning VM...\n"
}
```
#### script_complete
Script execution completed.
```json
{
"type": "script_complete",
"devbenchId": 123,
"exitCode": 0
}
```
#### status_update
DevBench status changed.
```json
{
"type": "status_update",
"devbenchId": 123,
"status": "active"
}
```
---
## Error Responses
### Standard Error Format
```json
{
"error": "Error message description"
}
```
### Common HTTP Status Codes
- `200`: Success
- `302`: Redirect
- `400`: Bad Request (validation error)
- `401`: Unauthorized (not authenticated)
- `403`: Forbidden (insufficient permissions)
- `404`: Not Found
- `500`: Internal Server Error
---
## Rate Limiting
Currently not implemented. Consider adding rate limiting for production:
- Login attempts: 5 per minute
- DevBench creation: 10 per hour per user
- API calls: 100 per minute per user
---
## Data Models
### User
```json
{
"id": 1,
"username": "admin",
"email": "admin@example.com",
"password": "$2a$10$...", // bcrypt hash
"is_admin": 1,
"created_at": "2025-11-23T12:00:00.000Z"
}
```
### DevBench
```json
{
"id": 1,
"user_id": 1,
"name": "test-vm",
"actual_name": "admin_test-vm",
"status": "active",
"ssh_info": "6004",
"vnc_info": "5004",
"created_at": "2025-11-23T12:00:00.000Z",
"updated_at": "2025-11-23T12:05:00.000Z"
}
```
---
## Examples
### Create DevBench with cURL
```bash
# Login first to get session cookie
curl -c cookies.txt -X POST http://localhost:9090/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123"
# Create DevBench
curl -b cookies.txt -X POST http://localhost:9090/create-devbench \
-H "Content-Type: application/json" \
-d '{"name":"test-vm"}'
```
### Check Status with cURL
```bash
curl -b cookies.txt http://localhost:9090/check-status/1
```
### Add User with cURL
```bash
curl -b cookies.txt -X POST http://localhost:9090/admin/add-user \
-H "Content-Type: application/json" \
-d '{
"username":"john",
"email":"john@example.com",
"password":"password123"
}'
```
### WebSocket with JavaScript
```javascript
// Connect
const ws = new WebSocket('ws://localhost:9090');
// Register
ws.onopen = () => {
fetch('/api/user-info')
.then(res => res.json())
.then(data => {
ws.send(JSON.stringify({
type: 'register',
userId: data.userId
}));
});
};
// Listen for messages
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
if (message.type === 'script_output') {
// Update UI with script output
document.getElementById('log').textContent += message.data;
}
};
```
---
## Security Considerations
### Authentication
- Session-based authentication
- HTTP-only cookies
- Password hashing with bcrypt (10 rounds)
### Authorization
- Role-based access control (Admin/User)
- Owner-only access to DevBenches
- Admin-only routes protected
### Input Validation
- Username: Letters only
- DevBench name: Alphanumeric, hyphens, underscores
- SQL injection prevention: Parameterized queries
- XSS prevention: EJS auto-escaping
### Best Practices
- Always use HTTPS in production
- Change default admin password
- Use strong session secret
- Implement rate limiting
- Add CSRF protection
- Enable security headers
---
## Changelog
### Version 1.0.0 (2025-11-23)
- Initial API release
- User authentication
- DevBench management
- Admin panel
- WebSocket support
- Help page
- Dark theme support

View File

@@ -0,0 +1,400 @@
# DevBench Manager - Architecture Documentation
## System Overview
DevBench Manager is a web-based application for managing virtual machine (VM) development environments. It provides a centralized interface for users to create, manage, and access their DevBench VMs through a secure web portal.
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ User Browser │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Dashboard │ │ Admin Panel │ │ Help Page │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│ HTTPS/WSS
┌─────────────────────────────────────────────────────────────────┐
│ Caddy Reverse Proxy │
│ (tbm.nabd-co.com) │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ DevBench Manager Container │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Express.js Server │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Routes │ │ WebSocket │ │ Auth │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ SQLite Database │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Users │ │ DevBenches │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Provision Script Executor │ │
│ │ (provision_vm.sh) │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│ SSH (Port 49152)
┌─────────────────────────────────────────────────────────────────┐
│ Remote VM Host Server │
│ (asf-server.duckdns.org) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ VirtualBox Manager │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ VM 1 │ │ VM 2 │ │ VM 3 │ │ VM N │ │ │
│ │ │ SSH: │ │ SSH: │ │ SSH: │ │ SSH: │ │ │
│ │ │ 6001 │ │ 6002 │ │ 6003 │ │ 600N │ │ │
│ │ │ VNC: │ │ VNC: │ │ VNC: │ │ VNC: │ │ │
│ │ │ 5001 │ │ 5002 │ │ 5003 │ │ 500N │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Component Architecture
### 1. Frontend Layer
#### Technologies
- **EJS Templates**: Server-side rendering
- **Bootstrap 5**: UI framework
- **Font Awesome**: Icons
- **Custom CSS**: Theming (light/dark mode)
- **Vanilla JavaScript**: Client-side interactivity
#### Components
- **Dashboard**: User's DevBench management interface
- **Admin Panel**: User and system management
- **Help Page**: Documentation and SSH config guide
- **Login Page**: Authentication interface
### 2. Backend Layer
#### Technologies
- **Node.js**: Runtime environment
- **Express.js**: Web framework
- **WebSocket (ws)**: Real-time communication
- **SQLite3**: Database
- **bcryptjs**: Password hashing
- **express-session**: Session management
#### Core Modules
##### Authentication Module
```javascript
- Session-based authentication
- Password hashing with bcrypt
- Role-based access control (Admin/User)
- Middleware for route protection
```
##### DevBench Management Module
```javascript
- Create DevBench
- Delete DevBench
- Activate DevBench
- Status monitoring
- Real-time log streaming
```
##### WebSocket Module
```javascript
- Real-time script output
- Status updates
- User-specific broadcasting
- Connection management
```
##### Provision Script Executor
```javascript
- SSH connection to remote host
- Script execution via sshpass
- Output parsing
- Error handling
- Timeout management (30 minutes)
```
### 3. Database Layer
#### Schema
**Users Table**
```sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
is_admin INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
```
**DevBenches Table**
```sql
CREATE TABLE devbenches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
actual_name TEXT,
status TEXT DEFAULT 'inactive',
ssh_info TEXT,
vnc_info TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
```
### 4. Infrastructure Layer
#### Docker Container
```yaml
- Base Image: Node.js 18
- Exposed Port: 3001 (mapped to 9090)
- Volumes:
- ./data:/app/data (Database persistence)
- ./logs:/app/logs (Log files)
- ./provision_vm.sh:/app/provision_vm.sh (Script)
- Networks:
- app-network (Internal)
- caddy_network (External proxy)
```
#### Reverse Proxy (Caddy)
```
- Domain: tbm.nabd-co.com
- SSL/TLS: Automatic (Let's Encrypt)
- Proxy Target: devbench-manager:3001
```
## Data Flow
### DevBench Creation Flow
```
1. User clicks "Create DevBench"
2. Frontend sends POST /create-devbench
3. Backend validates input
4. Database record created (status: creating)
5. WebSocket connection established
6. provision_vm.sh executed via SSH
7. Real-time output streamed via WebSocket
8. Script parses VM info (name, SSH port, VNC port)
9. Database updated with connection info
10. Frontend receives completion notification
11. Page refreshes to show active DevBench
```
### Authentication Flow
```
1. User submits login form
2. Backend validates credentials
3. Password compared with bcrypt
4. Session created on success
5. User redirected to dashboard/admin
6. Session cookie stored in browser
7. Subsequent requests include session
8. Middleware validates session
```
### Status Monitoring Flow
```
1. Periodic check (every 60 seconds)
2. For each DevBench in database
3. Execute provision_vm.sh status <vm_name>
4. Parse output for status
5. Update database if changed
6. Broadcast update via WebSocket
7. Frontend updates UI in real-time
```
## Security Architecture
### Authentication & Authorization
- **Password Hashing**: bcrypt with salt rounds
- **Session Management**: Secure HTTP-only cookies
- **Role-Based Access**: Admin vs User permissions
- **Route Protection**: Middleware guards
### Input Validation
- **Username**: Letters only, no special characters
- **DevBench Name**: Alphanumeric, hyphens, underscores
- **SQL Injection**: Parameterized queries
- **XSS Prevention**: EJS auto-escaping
### Network Security
- **HTTPS**: Enforced via Caddy
- **SSH**: Password-based (sshpass) with timeout
- **Container Isolation**: Docker networking
- **Port Exposure**: Minimal (only 9090)
## Scalability Considerations
### Current Limitations
- Single SQLite database (not suitable for high concurrency)
- SSH-based provisioning (sequential, not parallel)
- In-memory WebSocket connections (lost on restart)
- Single container deployment
### Future Improvements
- **Database**: Migrate to PostgreSQL/MySQL
- **Queue System**: Redis/RabbitMQ for async jobs
- **Load Balancing**: Multiple container instances
- **Persistent WebSocket**: Redis pub/sub
- **Caching**: Redis for session and status data
## Monitoring & Logging
### Application Logs
- Location: `/app/logs/`
- Format: Timestamped console output
- Rotation: Manual (not automated)
### Provision Script Logs
- Location: `/var/log/devbench/`
- Format: Timestamped with operation details
- Retention: Indefinite
### Health Checks
- Endpoint: `/health`
- Interval: 30 seconds
- Timeout: 10 seconds
- Retries: 3
## Technology Stack Summary
| Layer | Technology | Purpose |
|-------|-----------|---------|
| Frontend | EJS, Bootstrap 5, JavaScript | UI rendering and interactivity |
| Backend | Node.js, Express.js | Web server and API |
| Real-time | WebSocket (ws) | Live updates |
| Database | SQLite3 | Data persistence |
| Authentication | bcryptjs, express-session | Security |
| Container | Docker, Docker Compose | Deployment |
| Proxy | Caddy | SSL/TLS and routing |
| VM Management | Bash, SSH, VirtualBox | VM provisioning |
## Deployment Architecture
### Production Environment
```
Internet
Caddy (Port 443) → SSL Termination
Docker Network (caddy_network)
DevBench Manager Container (Port 3001)
SQLite Database (Volume: ./data)
SSH Connection (Port 49152)
Remote VM Host
```
### Development Environment
```
localhost:9090
DevBench Manager Container
Local SQLite Database
SSH to Remote Host
```
## Configuration Management
### Environment Variables
- `NODE_ENV`: production/development
- `PORT`: Application port (default: 3001)
- `SECRET_KEY`: Session secret
- `ADMIN_EMAIL`: Default admin email
- `ADMIN_PASSWORD`: Default admin password
### Configuration Files
- `config.js`: Application configuration
- `docker-compose.yml`: Container orchestration
- `Dockerfile`: Container build instructions
- `.env`: Environment variables (optional)
## Error Handling
### Application Errors
- Database errors: Logged and returned as 500
- Authentication errors: Returned as 401/403
- Validation errors: Returned as 400
- Script errors: Logged and broadcast via WebSocket
### Script Errors
- Timeout: 30 minutes (1800 seconds)
- SSH failures: Logged and status set to 'error'
- Parsing failures: Status set to 'error'
- Network issues: Retry not implemented
## Performance Considerations
### Bottlenecks
1. **SSH Connection**: Sequential, blocking
2. **Script Execution**: Long-running (up to 30 min)
3. **Status Checks**: Every 60 seconds for all VMs
4. **SQLite**: Limited concurrent writes
### Optimizations
- WebSocket for real-time updates (no polling)
- Session caching in memory
- Static asset serving via Express
- Minimal database queries
## Maintenance & Operations
### Backup Strategy
- Database: Copy `./data/devbench.db`
- Logs: Archive `./logs/` directory
- Configuration: Version control
### Update Procedure
1. Pull latest code
2. Run `./deploy.sh`
3. Verify health endpoint
4. Check logs for errors
### Troubleshooting
- Check container logs: `docker-compose logs -f`
- Verify network: `docker network inspect caddy_network`
- Test SSH: `ssh -p 49152 asf@asf-server.duckdns.org`
- Check database: `sqlite3 data/devbench.db`

View File

@@ -0,0 +1,679 @@
# DevBench Manager - Deployment Guide
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Quick Start](#quick-start)
3. [Detailed Deployment](#detailed-deployment)
4. [Configuration](#configuration)
5. [Verification](#verification)
6. [Troubleshooting](#troubleshooting)
7. [Maintenance](#maintenance)
8. [Backup & Recovery](#backup--recovery)
## Prerequisites
### Required Software
- **Docker**: Version 20.10 or higher
- **Docker Compose**: Version 2.0 or higher
- **Git**: For cloning the repository
- **SSH Access**: To remote VM host (asf-server.duckdns.org:49152)
### System Requirements
- **OS**: Linux (Ubuntu 20.04+ recommended), macOS, or Windows with WSL2
- **RAM**: Minimum 512MB for container
- **Disk**: Minimum 1GB free space
- **Network**: Internet connection for Docker images and SSH
### Access Requirements
- SSH credentials for VM host
- Domain name configured (optional, for Caddy)
- Port 9090 available (or configure different port)
## Quick Start
### One-Command Deployment
```bash
# Clone the repository
git clone <repository-url>
cd ASF_devbench
# Run deployment script
chmod +x deploy.sh
./deploy.sh
```
That's it! The application will be available at:
- **Direct**: http://localhost:9090
- **Via Caddy**: https://tbm.nabd-co.com (if configured)
### Default Credentials
- **Username**: admin
- **Password**: admin123
⚠️ **Important**: Change the default password after first login!
## Detailed Deployment
### Step 1: Clone Repository
```bash
git clone <repository-url>
cd ASF_devbench
```
### Step 2: Review Configuration
Check `config.js` for default settings:
```javascript
{
port: 3001,
database: { path: './data/devbench.db' },
session: { secret: 'your-secret-key' },
defaultAdmin: {
username: 'admin',
password: 'admin123',
email: 'admin@nabd-co.com'
}
}
```
### Step 3: Set Environment Variables (Optional)
Create `.env` file:
```bash
NODE_ENV=production
PORT=3001
SECRET_KEY=your-secure-secret-key
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=your-secure-password
```
### Step 4: Prepare Directories
```bash
# Create necessary directories
mkdir -p data logs public/downloads
# Set permissions
chmod 755 data logs
chmod +x provision_vm.sh deploy.sh
```
### Step 5: Configure Docker Network
```bash
# Create Caddy network (if using reverse proxy)
docker network create caddy_network
```
### Step 6: Deploy with Docker Compose
```bash
# Build and start container
docker-compose up -d --build
# Check status
docker-compose ps
# View logs
docker-compose logs -f
```
### Step 7: Verify Deployment
```bash
# Check health endpoint
curl http://localhost:9090/health
# Expected response:
# {"status":"ok","timestamp":"2025-11-23T...","version":"1.0.0"}
```
## Configuration
### Docker Compose Configuration
Edit `docker-compose.yml`:
```yaml
services:
devbench-manager:
ports:
- "9090:3001" # Change external port here
environment:
- NODE_ENV=production
- SECRET_KEY=${SECRET_KEY:-dev-secret-key}
volumes:
- ./data:/app/data
- ./logs:/app/logs
- ./provision_vm.sh:/app/provision_vm.sh:ro
```
### Caddy Reverse Proxy
Add to your Caddyfile:
```
tbm.nabd-co.com {
reverse_proxy devbench-manager:3001
}
```
Ensure Caddy container is on `caddy_network`:
```bash
# Check Caddy network
docker network inspect caddy_network
# Connect Caddy to network if needed
docker network connect caddy_network caddy-container-name
```
### SSH Configuration
The application connects to the VM host via SSH. Ensure:
1. **Host**: asf-server.duckdns.org
2. **Port**: 49152
3. **User**: asf
4. **Password**: ASF (configured in provision_vm.sh)
Test SSH connection:
```bash
ssh -p 49152 asf@asf-server.duckdns.org
```
### Firewall Configuration
Open required ports:
```bash
# For direct access
sudo ufw allow 9090/tcp
# For Caddy (if using)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
```
## Verification
### 1. Container Status
```bash
# Check if container is running
docker ps | grep devbench-manager
# Expected output:
# CONTAINER ID IMAGE STATUS PORTS
# abc123... devbench-manager:latest Up 2 minutes 0.0.0.0:9090->3001/tcp
```
### 2. Health Check
```bash
# Test health endpoint
curl http://localhost:9090/health
# Or with jq for formatted output
curl -s http://localhost:9090/health | jq
```
### 3. Network Connectivity
```bash
# Check networks
docker inspect devbench-manager --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'
# Expected: app-network caddy_network
```
### 4. Database
```bash
# Check if database was created
ls -la data/devbench.db
# Connect to database
docker exec -it devbench-manager sqlite3 /app/data/devbench.db
# Run query
sqlite> SELECT * FROM users;
```
### 5. Logs
```bash
# View application logs
docker-compose logs -f
# View last 50 lines
docker-compose logs --tail=50
# View specific service
docker-compose logs devbench-manager
```
### 6. Web Interface
1. Open browser: http://localhost:9090
2. Login with admin credentials
3. Create a test DevBench
4. Verify real-time log output
5. Check connection information
## Troubleshooting
### Container Won't Start
**Problem**: Container exits immediately
**Solution**:
```bash
# Check logs
docker-compose logs
# Common issues:
# 1. Port already in use
sudo lsof -i :9090
# Kill process or change port in docker-compose.yml
# 2. Permission issues
sudo chown -R $USER:$USER data logs
# 3. Missing provision script
ls -la provision_vm.sh
chmod +x provision_vm.sh
```
### Cannot Access Web Interface
**Problem**: Connection refused or timeout
**Solution**:
```bash
# 1. Check if container is running
docker ps | grep devbench-manager
# 2. Check port mapping
docker port devbench-manager
# 3. Test from inside container
docker exec devbench-manager wget -O- http://localhost:3001/health
# 4. Check firewall
sudo ufw status
sudo ufw allow 9090/tcp
# 5. Check if port is listening
sudo netstat -tlnp | grep 9090
```
### Caddy Proxy Not Working
**Problem**: Cannot access via domain
**Solution**:
```bash
# 1. Check if both containers are on caddy_network
docker network inspect caddy_network
# 2. Connect devbench-manager to network
docker network connect caddy_network devbench-manager
# 3. Restart Caddy
docker restart caddy-container-name
# 4. Check Caddy logs
docker logs caddy-container-name
# 5. Test from Caddy container
docker exec caddy-container-name wget -O- http://devbench-manager:3001/health
```
### SSH Connection Fails
**Problem**: Cannot connect to VM host
**Solution**:
```bash
# 1. Test SSH manually
ssh -p 49152 asf@asf-server.duckdns.org
# 2. Check if sshpass is installed in container
docker exec devbench-manager which sshpass
# 3. Check provision script
docker exec devbench-manager cat /app/provision_vm.sh | grep SSH_
# 4. Test from container
docker exec -it devbench-manager bash
./provision_vm.sh status test_vm
```
### Database Errors
**Problem**: Database locked or corrupted
**Solution**:
```bash
# 1. Stop container
docker-compose down
# 2. Backup database
cp data/devbench.db data/devbench.db.backup
# 3. Check database integrity
sqlite3 data/devbench.db "PRAGMA integrity_check;"
# 4. If corrupted, restore from backup or recreate
rm data/devbench.db
docker-compose up -d
```
### WebSocket Not Working
**Problem**: Real-time updates not appearing
**Solution**:
```bash
# 1. Check browser console for errors
# Open DevTools → Console
# 2. Verify WebSocket connection
# Look for "WebSocket connected" message
# 3. Check if behind proxy
# Ensure proxy supports WebSocket upgrade
# 4. Test WebSocket endpoint
wscat -c ws://localhost:9090
```
## Maintenance
### Regular Tasks
#### Daily
- Monitor logs for errors
- Check disk space
- Verify health endpoint
#### Weekly
- Review user activity
- Clean up old logs
- Check for updates
#### Monthly
- Backup database
- Review security
- Update dependencies
### Updating the Application
```bash
# 1. Pull latest changes
git pull origin main
# 2. Backup database
cp data/devbench.db data/devbench.db.$(date +%Y%m%d)
# 3. Rebuild and restart
docker-compose down
docker-compose up -d --build
# 4. Verify
curl http://localhost:9090/health
```
### Viewing Logs
```bash
# Real-time logs
docker-compose logs -f
# Last 100 lines
docker-compose logs --tail=100
# Specific time range
docker-compose logs --since 2h
# Save logs to file
docker-compose logs > logs/docker-$(date +%Y%m%d).log
```
### Cleaning Up
```bash
# Remove stopped containers
docker container prune -f
# Remove unused images
docker image prune -a -f
# Remove unused volumes
docker volume prune -f
# Clean everything
docker system prune -a -f
```
### Scaling (Future)
For multiple instances:
```bash
# Scale to 3 instances
docker-compose up -d --scale devbench-manager=3
# Use load balancer (nginx/haproxy)
# Configure session persistence
```
## Backup & Recovery
### Backup Strategy
#### Database Backup
```bash
# Manual backup
cp data/devbench.db backups/devbench-$(date +%Y%m%d-%H%M%S).db
# Automated backup script
#!/bin/bash
BACKUP_DIR="backups"
mkdir -p $BACKUP_DIR
cp data/devbench.db $BACKUP_DIR/devbench-$(date +%Y%m%d).db
# Keep only last 7 days
find $BACKUP_DIR -name "devbench-*.db" -mtime +7 -delete
```
#### Full Backup
```bash
# Backup everything
tar -czf devbench-backup-$(date +%Y%m%d).tar.gz \
data/ \
logs/ \
config.js \
.env \
docker-compose.yml
```
### Recovery
#### Restore Database
```bash
# 1. Stop container
docker-compose down
# 2. Restore database
cp backups/devbench-20251123.db data/devbench.db
# 3. Start container
docker-compose up -d
# 4. Verify
curl http://localhost:9090/health
```
#### Full Recovery
```bash
# 1. Extract backup
tar -xzf devbench-backup-20251123.tar.gz
# 2. Deploy
./deploy.sh
# 3. Verify all services
```
### Disaster Recovery
#### Complete System Failure
```bash
# 1. Fresh installation
git clone <repository-url>
cd ASF_devbench
# 2. Restore backups
cp /backup/location/devbench.db data/
cp /backup/location/.env .
# 3. Deploy
./deploy.sh
# 4. Verify users and DevBenches
```
## Security Hardening
### Change Default Credentials
```bash
# 1. Login as admin
# 2. Go to admin panel
# 3. Reset admin password
# Or use API:
curl -X POST http://localhost:9090/admin/reset-password/1 \
-H "Content-Type: application/json" \
-d '{"newPassword":"new-secure-password"}'
```
### Update Session Secret
Edit `.env`:
```bash
SECRET_KEY=$(openssl rand -base64 32)
```
Restart:
```bash
docker-compose restart
```
### Enable HTTPS
Use Caddy for automatic HTTPS:
```
tbm.nabd-co.com {
reverse_proxy devbench-manager:3001
}
```
### Restrict Access
Use firewall:
```bash
# Allow only specific IPs
sudo ufw allow from 192.168.1.0/24 to any port 9090
```
## Monitoring
### Health Monitoring
```bash
# Simple health check script
#!/bin/bash
if curl -sf http://localhost:9090/health > /dev/null; then
echo "OK"
else
echo "FAILED"
# Send alert
fi
```
### Log Monitoring
```bash
# Watch for errors
docker-compose logs -f | grep -i error
# Count errors
docker-compose logs | grep -i error | wc -l
```
### Resource Monitoring
```bash
# Container stats
docker stats devbench-manager
# Disk usage
du -sh data/ logs/
# Database size
ls -lh data/devbench.db
```
## Performance Tuning
### Database Optimization
```bash
# Vacuum database
docker exec devbench-manager sqlite3 /app/data/devbench.db "VACUUM;"
# Analyze database
docker exec devbench-manager sqlite3 /app/data/devbench.db "ANALYZE;"
```
### Container Resources
Edit `docker-compose.yml`:
```yaml
services:
devbench-manager:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
```
## Support
### Getting Help
1. Check logs: `docker-compose logs`
2. Review documentation in `/docs`
3. Check GitHub issues
4. Contact system administrator
### Reporting Issues
Include:
- Docker version: `docker --version`
- Docker Compose version: `docker-compose --version`
- Container logs: `docker-compose logs`
- Error messages
- Steps to reproduce

View File

@@ -0,0 +1,530 @@
# DevBench Manager - Project Structure
## Directory Tree
```
ASF_devbench/
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # System architecture
│ ├── STRUCTURE.md # This file
│ ├── DEPLOYMENT.md # Deployment guide
│ └── API.md # API documentation
├── public/ # Static assets
│ ├── css/
│ │ └── style.css # Custom styles + dark theme
│ ├── images/
│ │ ├── nabd-logo.svg # Company logo (light)
│ │ ├── nabd-logo-white.svg # Company logo (dark)
│ │ └── tbm-icon.png # TBM branding icon
│ └── downloads/
│ └── db_vm_ssh_config_manager.exe # SSH config tool
├── views/ # EJS templates
│ ├── layout.ejs # Base layout with navbar
│ ├── login.ejs # Login page
│ ├── dashboard.ejs # User dashboard
│ ├── admin.ejs # Admin panel
│ └── help.ejs # Help/documentation page
├── data/ # Database storage (created on deploy)
│ └── devbench.db # SQLite database
├── logs/ # Application logs (created on deploy)
│ └── *.log # Log files
├── server.js # Main application server
├── config.js # Configuration settings
├── package.json # Node.js dependencies
├── provision_vm.sh # VM provisioning script
├── Dockerfile # Docker image definition
├── docker-compose.yml # Container orchestration
├── .dockerignore # Docker build exclusions
├── deploy.sh # Deployment script
├── install.sh # Local installation script
├── start.sh # Start script (Docker/Node)
├── cleanup.sh # Cleanup script
├── .env.example # Environment variables template
├── .gitignore # Git exclusions
├── README.md # Main documentation
└── CHANGELOG.md # Change history
```
## File Descriptions
### Core Application Files
#### `server.js`
**Purpose**: Main Express.js application server
**Key Components**:
- Express app initialization
- Database setup and schema
- Route definitions
- WebSocket server
- Authentication middleware
- DevBench management logic
- Provision script executor
- Status monitoring (60-second interval)
**Dependencies**:
- express
- express-session
- sqlite3
- bcryptjs
- ws (WebSocket)
- child_process (for script execution)
**Key Functions**:
```javascript
- requireAuth() // Authentication middleware
- requireAdmin() // Admin authorization middleware
- executeProvisionScript() // Execute VM provisioning
- broadcastToUser() // WebSocket messaging
```
#### `config.js`
**Purpose**: Centralized configuration
**Settings**:
- Server port
- Database path
- Session configuration
- Default admin credentials
- Validation patterns
- Provision script settings
#### `provision_vm.sh`
**Purpose**: VM provisioning and management
**Commands**:
- `create <vm_name>`: Create new VM
- `status <vm_name>`: Check VM status
- `delete <vm_name>`: Delete VM
- `activate <vm_name>`: Activate VM
- `start <vm_name>`: Start VM
- `stop <vm_name>`: Stop VM
**Features**:
- SSH connection to remote host
- Output logging
- Connection info extraction
- Error handling
- Timeout management (30 minutes)
### Frontend Files
#### `views/layout.ejs`
**Purpose**: Base template for all pages
**Features**:
- Navigation bar with logo
- Help link
- Theme toggle button
- WebSocket initialization
- Bootstrap and Font Awesome imports
- Dark theme JavaScript
#### `views/dashboard.ejs`
**Purpose**: User dashboard
**Features**:
- DevBench list with cards
- Create DevBench modal
- Connection info display (SSH/VNC ports)
- Real-time log output
- Status indicators
- Copy-to-clipboard functionality
#### `views/admin.ejs`
**Purpose**: Admin panel
**Features**:
- User management table
- Add user modal
- DevBench overview table
- Password reset
- User deletion
- DevBench deletion
#### `views/help.ejs`
**Purpose**: Help and documentation
**Features**:
- SSH Config Manager guide
- Step-by-step instructions
- Download link for tool
- Connection information
- Important notes
#### `views/login.ejs`
**Purpose**: Authentication page
**Features**:
- Login form
- Error messages
- Branding
### Static Assets
#### `public/css/style.css`
**Purpose**: Custom styling
**Features**:
- NABD brand colors
- Dark theme styles
- Component styling
- Responsive design
- Smooth transitions
#### `public/images/`
**Purpose**: Image assets
**Files**:
- `nabd-logo.svg`: Light theme logo
- `nabd-logo-white.svg`: Dark theme logo
- `tbm-icon.png`: Branding icon and favicon
#### `public/downloads/`
**Purpose**: Downloadable files
**Files**:
- `db_vm_ssh_config_manager.exe`: SSH configuration tool
### Deployment Files
#### `Dockerfile`
**Purpose**: Docker image definition
**Base Image**: node:18-alpine
**Steps**:
1. Set working directory
2. Copy package files
3. Install dependencies
4. Copy application files
5. Create data/logs directories
6. Expose port 3001
7. Set start command
#### `docker-compose.yml`
**Purpose**: Container orchestration
**Configuration**:
- Service: devbench-manager
- Port mapping: 9090:3001
- Volumes: data, logs, provision script
- Networks: app-network, caddy_network
- Health check: /health endpoint
- Restart policy: unless-stopped
#### `deploy.sh`
**Purpose**: Automated deployment
**Steps**:
1. Create directories
2. Set permissions
3. Clean up old containers
4. Create Docker network
5. Build and start container
6. Wait for startup
7. Verify health
8. Display access information
### Configuration Files
#### `package.json`
**Purpose**: Node.js project configuration
**Scripts**:
- `start`: Run production server
- `dev`: Run with nodemon (auto-reload)
**Dependencies**:
- express: Web framework
- express-session: Session management
- bcryptjs: Password hashing
- sqlite3: Database
- body-parser: Request parsing
- ejs: Template engine
- ws: WebSocket
- child_process: Script execution
#### `.env.example`
**Purpose**: Environment variables template
**Variables**:
- NODE_ENV
- PORT
- SECRET_KEY
- ADMIN_EMAIL
- ADMIN_PASSWORD
#### `.gitignore`
**Purpose**: Git exclusions
**Excluded**:
- node_modules/
- data/
- logs/
- .env
- *.log
- .DS_Store
### Utility Scripts
#### `install.sh`
**Purpose**: Local installation
**Actions**:
- Check Node.js version
- Install npm dependencies
- Display start instructions
#### `start.sh`
**Purpose**: Start application
**Logic**:
- Check for Docker
- Start with Docker Compose if available
- Fall back to Node.js
- Install dependencies if needed
#### `cleanup.sh`
**Purpose**: Clean up containers
**Actions**:
- Stop containers
- Remove containers
- Prune dangling containers
- List volumes
### Documentation Files
#### `README.md`
**Purpose**: Main project documentation
**Sections**:
- Features overview
- Installation instructions
- Configuration guide
- Usage instructions
- API endpoints
- Troubleshooting
#### `CHANGELOG.md`
**Purpose**: Change history
**Content**:
- Recent updates
- SSH configuration changes
- Output format changes
- New features
- Migration notes
#### `docs/ARCHITECTURE.md`
**Purpose**: System architecture
**Content**:
- Architecture diagrams
- Component descriptions
- Data flow
- Security architecture
- Technology stack
#### `docs/DEPLOYMENT.md`
**Purpose**: Deployment guide
**Content**:
- Prerequisites
- Deployment steps
- Configuration
- Troubleshooting
- Maintenance
## Data Flow Through Files
### DevBench Creation
```
1. views/dashboard.ejs
↓ (User clicks Create)
2. server.js (POST /create-devbench)
↓ (Validates and creates DB record)
3. server.js (executeProvisionScript)
↓ (Spawns child process)
4. provision_vm.sh
↓ (SSH to remote host)
5. Remote VM Host
↓ (Creates VM, returns info)
6. provision_vm.sh
↓ (Parses output)
7. server.js
↓ (Updates database)
8. WebSocket
↓ (Broadcasts to user)
9. views/dashboard.ejs
↓ (Updates UI)
```
### Authentication
```
1. views/login.ejs
↓ (User submits form)
2. server.js (POST /login)
↓ (Validates credentials)
3. config.js (validation patterns)
4. server.js (bcrypt compare)
↓ (Creates session)
5. express-session
↓ (Stores session)
6. server.js (redirect)
7. views/dashboard.ejs or views/admin.ejs
```
### Theme Toggle
```
1. views/layout.ejs (Theme toggle button)
↓ (User clicks)
2. JavaScript (theme toggle handler)
↓ (Toggles class)
3. public/css/style.css (dark-theme styles)
↓ (Applies styles)
4. localStorage
↓ (Saves preference)
```
## Key Directories
### `/data`
- **Created**: On first deployment
- **Purpose**: SQLite database storage
- **Persistence**: Docker volume
- **Backup**: Copy devbench.db file
### `/logs`
- **Created**: On first deployment
- **Purpose**: Application logs
- **Persistence**: Docker volume
- **Rotation**: Manual
### `/public`
- **Purpose**: Static assets served by Express
- **Access**: Direct URL paths
- **Caching**: Browser caching enabled
### `/views`
- **Purpose**: EJS templates
- **Rendering**: Server-side
- **Access**: Via routes only
### `/docs`
- **Purpose**: Project documentation
- **Format**: Markdown
- **Access**: File system only
## Configuration Hierarchy
```
1. Environment Variables (.env)
2. config.js (defaults)
3. docker-compose.yml (container env)
4. Application runtime
```
## Port Mapping
```
External → Container → Application
9090 → 3001 → Express Server
```
## Network Architecture
```
Internet
Caddy (caddy_network)
DevBench Manager (caddy_network + app-network)
SQLite (local file)
SSH (external)
Remote VM Host
```
## File Permissions
### Scripts
- `provision_vm.sh`: 755 (executable)
- `deploy.sh`: 755 (executable)
- `install.sh`: 755 (executable)
- `start.sh`: 755 (executable)
- `cleanup.sh`: 755 (executable)
### Directories
- `data/`: 755
- `logs/`: 755
- `public/`: 755
### Database
- `devbench.db`: 644 (read/write for owner)
## Build Process
### Docker Build
```
1. Read Dockerfile
2. Pull node:18-alpine
3. Copy package.json
4. npm install
5. Copy application files
6. Set permissions
7. Create image
```
### Docker Compose
```
1. Read docker-compose.yml
2. Create networks
3. Build image (if needed)
4. Create container
5. Mount volumes
6. Start container
7. Run health checks
```
## Runtime Process
### Container Startup
```
1. Docker starts container
2. Node.js starts
3. server.js executes
4. Database initialized
5. Express server starts
6. WebSocket server starts
7. Health check passes
8. Ready for connections
```
### Request Handling
```
1. Request arrives at Caddy
2. Proxied to container
3. Express routes request
4. Middleware checks auth
5. Controller handles logic
6. Database query (if needed)
7. Template rendered
8. Response sent
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -0,0 +1,38 @@
#!/bin/bash
echo "Installing DevBench Manager..."
# Check if Node.js is installed
if ! command -v node &> /dev/null; then
echo "Node.js is not installed. Please install Node.js 18+ first."
echo "Visit: https://nodejs.org/"
exit 1
fi
# Check Node.js version
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [ "$NODE_VERSION" -lt 18 ]; then
echo "Node.js version 18 or higher is required. Current version: $(node -v)"
exit 1
fi
echo "Node.js version: $(node -v)"
# Install npm dependencies
echo "Installing npm dependencies..."
npm install
if [ $? -eq 0 ]; then
echo "✓ Dependencies installed successfully!"
echo ""
echo "Setup complete! You can now start the application with:"
echo " npm start"
echo " or"
echo " ./start.sh"
echo ""
echo "The application will be available at: http://localhost:3001"
echo "Default admin login: admin / admin123"
else
echo "✗ Failed to install dependencies"
exit 1
fi

View File

@@ -0,0 +1,26 @@
{
"name": "devbench-manager",
"version": "1.0.0",
"description": "DevBench Manager Web Application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"express-session": "^1.17.3",
"bcryptjs": "^2.4.3",
"sqlite3": "^5.1.6",
"body-parser": "^1.20.2",
"ejs": "^3.1.9",
"child_process": "^1.0.2",
"ws": "^8.14.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"keywords": ["devbench", "vm", "manager"],
"author": "",
"license": "MIT"
}

View File

@@ -0,0 +1,155 @@
#!/bin/bash
# Set up logging
LOG_DIR="/var/log/devbench"
LOG_FILE="$LOG_DIR/provision_vm_$(date +%Y%m%d_%H%M%S).log"
# Create log directory if it doesn't exist
mkdir -p "$LOG_DIR"
chmod 777 "$LOG_DIR"
# Function to log messages
log() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}
log "Starting provision_vm.sh with arguments: $*"
# Check if enough arguments were provided for the remote script.
# The remote 'provision_vm.sh' script expects at least 2 arguments: <command> <vm_name>.
if [ "$#" -lt 2 ]; then
error_msg="Error: Please provide at least two arguments for the remote command."
log "$error_msg"
echo "Usage: $0 <command> <vm_name>"
echo "Example: $0 create username_vmname"
echo " $0 status username_vmname"
exit 1
fi
# SSH connection details
SSH_USER="asf"
SSH_HOST="asf-server.duckdns.org"
SSH_PORT="49152"
SSH_PASS="ASF"
# The path to the script on the remote server
REMOTE_SCRIPT_PATH="./provision_vm.sh"
# Get command and VM name from arguments
COMMAND="$1"
VM_NAME="$2"
# Validate command
if [[ ! "$COMMAND" =~ ^(create|status|delete|start|stop|activate)$ ]]; then
log "Error: Invalid command '$COMMAND'. Must be one of: create, status, delete, start, stop, activate"
exit 1
fi
# Validate VM name format (username_vmname)
if [[ ! "$VM_NAME" =~ ^[a-zA-Z0-9_]+_[a-zA-Z0-9_-]+$ ]]; then
log "Error: Invalid VM name format. Must be in format: username_vmname"
exit 1
fi
# Extract username from VM name (everything before first underscore)
USERNAME="${VM_NAME%%_*}"
log "Command: $COMMAND, VM: $VM_NAME, User: $USERNAME"
# Check if sshpass is available
if ! command -v sshpass &> /dev/null; then
log "Error: sshpass is not installed. Please install it with 'apt-get install sshpass'"
exit 1
fi
# Check if we can resolve the host
if ! getent hosts "$SSH_HOST" >/dev/null 2>&1; then
log "Error: Cannot resolve host $SSH_HOST"
exit 1
fi
# Create a temporary file to capture output
TEMP_OUTPUT=$(mktemp)
log "Temporary output file: $TEMP_OUTPUT"
# Function to clean up temp file
cleanup() {
if [ -f "$TEMP_OUTPUT" ]; then
rm -f "$TEMP_OUTPUT"
log "Temporary files cleaned up"
fi
}
# Set up trap to ensure cleanup happens on script exit
trap cleanup EXIT
# Build the full SSH command
SSH_CMD="ssh -p $SSH_PORT -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=60 -o ServerAliveCountMax=30"
FULL_CMD="$REMOTE_SCRIPT_PATH $COMMAND $VM_NAME"
log "Executing remote command: $FULL_CMD"
# Execute the remote command with sshpass and capture output
{
# Increase timeout to 1800 seconds (30 minutes) and ensure we capture all output
if ! output=$(timeout 1800 sshpass -p "$SSH_PASS" $SSH_CMD "$SSH_USER@$SSH_HOST" "$FULL_CMD" 2>&1); then
EXIT_CODE=$?
# Capture any partial output even if command failed
echo "$output"
if [ $EXIT_CODE -eq 124 ]; then
log "ERROR: Command timed out after 30 minutes"
exit 124
else
log "ERROR: Command failed with exit code $EXIT_CODE"
exit $EXIT_CODE
fi
else
# Command succeeded, output the result
echo "$output"
fi
} | tee "$TEMP_OUTPUT"
# Process the output to extract connection information
SSH_PORT=""
VNC_PORT=""
VM_NAME_ACTUAL=""
# Read the output line by line to parse connection info
while IFS= read -r line; do
# Extract SSH port (e.g., "SSH Port: 6004")
if [[ $line == *"SSH Port:"* ]]; then
SSH_PORT=$(echo "$line" | grep -oE 'SSH Port: [0-9]+' | cut -d' ' -f3)
# Extract VNC port (e.g., "VNC Port: 5004")
elif [[ $line == *"VNC Port:"* ]]; then
VNC_PORT=$(echo "$line" | grep -oE 'VNC Port: [0-9]+' | cut -d' ' -f3)
# Extract VM name from success message
elif [[ $line == *"VM"*"Created and Ready"* ]]; then
VM_NAME_ACTUAL=$(echo "$line" | sed -n 's/.*VM \([^ ]*\) Created and Ready.*/\1/p')
fi
# Log each line to the log file
log "$line"
done < "$TEMP_OUTPUT"
# Output the connection information in a structured format
echo "VM_CREATION_COMPLETE"
if [ -n "$VM_NAME_ACTUAL" ]; then
echo "VM_NAME=$VM_NAME_ACTUAL"
fi
if [ -n "$SSH_PORT" ]; then
echo "SSH_PORT=$SSH_PORT"
fi
if [ -n "$VNC_PORT" ]; then
echo "VNC_PORT=$VNC_PORT"
fi
# Check if the command was successful
if [ ${PIPESTATUS[0]} -eq 0 ]; then
log "Remote command executed successfully"
exit 0
else
log "Remote command failed"
exit 1
fi
log "Provisioning script completed successfully"

View File

@@ -0,0 +1,308 @@
/* Custom styles for DevBench Manager */
.navbar-brand {
font-weight: bold;
color: #ffffff !important;
display: flex;
align-items: center;
}
.navbar-brand img {
height: 40px;
margin-right: 10px;
}
.company-logo {
max-height: 40px;
width: auto;
}
.status-active {
color: #28a745;
}
.status-inactive {
color: #dc3545;
}
.status-creating {
color: #ffc107;
}
.log-output {
background-color: #1e1e1e;
color: #ffffff;
font-family: 'Courier New', monospace;
font-size: 12px;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow-y: auto;
white-space: pre-wrap;
}
.connection-info {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 10px;
margin-top: 10px;
}
.connection-info code {
background-color: #e9ecef;
padding: 2px 4px;
border-radius: 3px;
word-break: break-all;
}
.login-card {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.brand-colors {
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* NABD Solutions brand colors */
.nabd-orange {
color: #f39c12;
}
.nabd-blue {
color: #1a365d;
}
.bg-nabd-gradient {
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
}
/* Dark blue theme overrides */
.bg-primary {
background-color: #1a365d !important;
}
.btn-primary {
background-color: #1a365d;
border-color: #1a365d;
}
.btn-primary:hover {
background-color: #2c5282;
border-color: #2c5282;
}
.btn-primary:focus {
background-color: #2c5282;
border-color: #2c5282;
box-shadow: 0 0 0 0.2rem rgba(26, 54, 93, 0.25);
}
.btn-outline-primary {
color: #1a365d;
border-color: #1a365d;
}
.btn-outline-primary:hover {
background-color: #1a365d;
border-color: #1a365d;
}
.text-primary {
color: #1a365d !important;
}
.card-header.bg-primary {
background-color: #1a365d !important;
}
/* Password info styling */
.connection-info small {
display: block;
margin-top: 5px;
padding: 3px 8px;
background-color: #e8f4fd;
border-radius: 3px;
border-left: 3px solid #1a365d;
}
/* ============================================
DARK THEME
============================================ */
/* Dark theme toggle button */
.theme-toggle {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
width: 50px;
height: 50px;
border-radius: 50%;
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
border: none;
color: white;
font-size: 20px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.theme-toggle:hover {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
}
/* Dark theme styles */
body.dark-theme {
background-color: #1a1a1a;
color: #e0e0e0;
}
body.dark-theme .navbar {
background-color: #0d1b2a !important;
}
body.dark-theme .card {
background-color: #2d2d2d;
border-color: #404040;
color: #e0e0e0;
}
body.dark-theme .card-header {
background-color: #1a365d;
border-color: #404040;
color: #ffffff;
}
body.dark-theme .card-header.bg-info {
background-color: #0c5460 !important;
}
body.dark-theme .card-header.bg-warning {
background-color: #856404 !important;
}
body.dark-theme .table {
color: #e0e0e0;
}
body.dark-theme .table-striped tbody tr:nth-of-type(odd) {
background-color: #333333;
}
body.dark-theme .table-striped tbody tr:nth-of-type(even) {
background-color: #2d2d2d;
}
body.dark-theme .modal-content {
background-color: #2d2d2d;
color: #e0e0e0;
}
body.dark-theme .modal-header {
border-bottom-color: #404040;
}
body.dark-theme .modal-footer {
border-top-color: #404040;
}
body.dark-theme .form-control {
background-color: #1a1a1a;
border-color: #404040;
color: #e0e0e0;
}
body.dark-theme .form-control:focus {
background-color: #1a1a1a;
border-color: #1a365d;
color: #e0e0e0;
}
body.dark-theme .connection-info {
background-color: #1a1a1a;
border-color: #404040;
}
body.dark-theme .connection-info code {
background-color: #0d1b2a;
color: #f39c12;
}
body.dark-theme .alert-info {
background-color: #0c5460;
border-color: #0c5460;
color: #d1ecf1;
}
body.dark-theme .alert-info .alert-link {
color: #bee5eb;
}
body.dark-theme .text-muted {
color: #999999 !important;
}
body.dark-theme .btn-secondary {
background-color: #404040;
border-color: #404040;
}
body.dark-theme .btn-secondary:hover {
background-color: #555555;
border-color: #555555;
}
body.dark-theme .btn-outline-primary {
color: #f39c12;
border-color: #f39c12;
}
body.dark-theme .btn-outline-primary:hover {
background-color: #f39c12;
border-color: #f39c12;
color: #1a1a1a;
}
body.dark-theme .login-card {
background-color: #2d2d2d;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}
body.dark-theme .form-text {
color: #999999;
}
body.dark-theme .connection-info small {
background-color: #0d1b2a;
border-left-color: #f39c12;
color: #e0e0e0;
}
body.dark-theme .help-steps code {
background-color: #0d1b2a;
color: #f39c12;
}
body.dark-theme .help-steps strong {
color: #f39c12;
}
body.dark-theme pre {
background-color: #0d1b2a !important;
color: #e0e0e0;
}
/* Smooth transitions for theme switching */
body,
.card,
.modal-content,
.form-control,
.connection-info,
.table {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

View File

@@ -0,0 +1,13 @@
<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
<!-- Pulse/Wave lines in orange -->
<g stroke="#f39c12" stroke-width="3" fill="none">
<!-- Main pulse line -->
<path d="M10 30 L25 30 L30 15 L35 45 L40 10 L45 50 L50 30 L65 30"/>
</g>
<!-- NABD text in white for navbar -->
<text x="75" y="35" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="white">NABD</text>
<!-- SOLUTIONS text in white -->
<text x="75" y="50" font-family="Arial, sans-serif" font-size="10" font-weight="normal" fill="white" letter-spacing="2px">SOLUTIONS</text>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -0,0 +1,13 @@
<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
<!-- Pulse/Wave lines in orange -->
<g stroke="#f39c12" stroke-width="3" fill="none">
<!-- Main pulse line -->
<path d="M10 30 L25 30 L30 15 L35 45 L40 10 L45 50 L50 30 L65 30"/>
</g>
<!-- NABD text in blue -->
<text x="75" y="35" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#2980b9">NABD</text>
<!-- SOLUTIONS text in blue -->
<text x="75" y="50" font-family="Arial, sans-serif" font-size="10" font-weight="normal" fill="#2980b9" letter-spacing="2px">SOLUTIONS</text>
</svg>

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -0,0 +1,593 @@
const express = require('express');
const session = require('express-session');
const bcrypt = require('bcryptjs');
const sqlite3 = require('sqlite3').verbose();
const bodyParser = require('body-parser');
const { spawn } = require('child_process');
const WebSocket = require('ws');
const http = require('http');
const path = require('path');
const config = require('./config');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// Database setup
const db = new sqlite3.Database(config.database.path);
// Initialize database tables
db.serialize(() => {
// Users table
db.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
is_admin INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// DevBenches table
db.run(`CREATE TABLE IF NOT EXISTS devbenches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
actual_name TEXT,
status TEXT DEFAULT 'inactive',
ssh_info TEXT,
vnc_info TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)`);
// Create default admin user if not exists
const adminPassword = bcrypt.hashSync(config.defaultAdmin.password, 10);
db.run(`INSERT OR IGNORE INTO users (username, email, password, is_admin)
VALUES (?, ?, ?, 1)`, [config.defaultAdmin.username, config.defaultAdmin.email, adminPassword]);
});
// Middleware
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static('public'));
app.set('view engine', 'ejs');
app.use(session({
secret: config.session.secret,
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: config.session.maxAge }
}));
// Authentication middleware
const requireAuth = (req, res, next) => {
if (req.session.userId) {
next();
} else {
res.redirect('/login');
}
};
const requireAdmin = (req, res, next) => {
if (req.session.userId && req.session.isAdmin) {
next();
} else {
res.status(403).send('Access denied');
}
};
// WebSocket connections for real-time updates
const clients = new Map();
wss.on('connection', (ws, req) => {
console.log('WebSocket connection established');
ws.on('message', (message) => {
try {
const data = JSON.parse(message);
if (data.type === 'register' && data.userId) {
clients.set(data.userId, ws);
console.log(`User ${data.userId} registered for WebSocket updates`);
}
} catch (error) {
console.error('WebSocket message error:', error);
}
});
ws.on('close', () => {
// Remove this connection from all users
for (const [userId, client] of clients.entries()) {
if (client === ws) {
clients.delete(userId);
console.log(`User ${userId} WebSocket disconnected`);
break;
}
}
});
});
// Broadcast to specific user
const broadcastToUser = (userId, message) => {
const client = clients.get(userId);
if (client && client.readyState === WebSocket.OPEN) {
try {
client.send(JSON.stringify(message));
console.log(`Sent message to user ${userId}:`, message.type);
} catch (error) {
console.error(`Error sending message to user ${userId}:`, error);
clients.delete(userId);
}
} else {
console.log(`No active WebSocket for user ${userId}`);
}
};
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
// User info endpoint for WebSocket registration
app.get('/api/user-info', requireAuth, (req, res) => {
res.json({
userId: req.session.userId,
username: req.session.username,
isAdmin: req.session.isAdmin
});
});
// Routes
app.get('/', (req, res) => {
if (req.session.userId) {
if (req.session.isAdmin) {
res.redirect('/admin');
} else {
res.redirect('/dashboard');
}
} else {
res.redirect('/login');
}
});
app.get('/login', (req, res) => {
res.render('login', { error: null });
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
if (err || !user || !bcrypt.compareSync(password, user.password)) {
return res.render('login', { error: 'Invalid username or password' });
}
req.session.userId = user.id;
req.session.username = user.username;
req.session.isAdmin = user.is_admin === 1;
if (user.is_admin) {
res.redirect('/admin');
} else {
res.redirect('/dashboard');
}
});
});
app.get('/logout', (req, res) => {
req.session.destroy();
res.redirect('/login');
});
// Admin routes
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
db.all(`SELECT u.*, COUNT(d.id) as devbench_count
FROM users u
LEFT JOIN devbenches d ON u.id = d.user_id
WHERE u.is_admin = 0
GROUP BY u.id`, (err, users) => {
if (err) {
console.error(err);
return res.status(500).send('Database error');
}
db.all(`SELECT d.*, u.username
FROM devbenches d
JOIN users u ON d.user_id = u.id
ORDER BY d.created_at DESC`, (err, devbenches) => {
if (err) {
console.error(err);
return res.status(500).send('Database error');
}
res.render('admin', { users, devbenches });
});
});
});
app.post('/admin/add-user', requireAuth, requireAdmin, (req, res) => {
const { username, email, password } = req.body;
// Validate username (no spaces, numbers, or special characters)
if (!config.validation.username.test(username)) {
return res.status(400).json({ error: 'Username must contain only letters' });
}
const hashedPassword = bcrypt.hashSync(password, 10);
db.run('INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
[username, email, hashedPassword], function(err) {
if (err) {
if (err.code === 'SQLITE_CONSTRAINT') {
return res.status(400).json({ error: 'Username already exists' });
}
return res.status(500).json({ error: 'Database error' });
}
res.json({ success: true });
});
});
app.post('/admin/delete-user/:id', requireAuth, requireAdmin, (req, res) => {
const userId = req.params.id;
// Delete user's devbenches first
db.run('DELETE FROM devbenches WHERE user_id = ?', [userId], (err) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
// Delete user
db.run('DELETE FROM users WHERE id = ? AND is_admin = 0', [userId], (err) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
res.json({ success: true });
});
});
});
app.post('/admin/reset-password/:id', requireAuth, requireAdmin, (req, res) => {
const userId = req.params.id;
const { newPassword } = req.body;
const hashedPassword = bcrypt.hashSync(newPassword, 10);
db.run('UPDATE users SET password = ? WHERE id = ? AND is_admin = 0',
[hashedPassword, userId], (err) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
res.json({ success: true });
});
});
// Help page
app.get('/help', requireAuth, (req, res) => {
res.render('help', {
username: req.session.username
});
});
// User dashboard
app.get('/dashboard', requireAuth, (req, res) => {
if (req.session.isAdmin) {
return res.redirect('/admin');
}
db.all('SELECT * FROM devbenches WHERE user_id = ? ORDER BY created_at DESC',
[req.session.userId], (err, devbenches) => {
if (err) {
console.error(err);
return res.status(500).send('Database error');
}
res.render('dashboard', {
username: req.session.username,
devbenches
});
});
});
// DevBench operations
app.post('/create-devbench', requireAuth, (req, res) => {
const { name } = req.body;
// Validate devbench name (only letters, numbers, hyphens, underscores)
if (!config.validation.devbenchName.test(name)) {
return res.status(400).json({
error: 'DevBench name can only contain letters, numbers, hyphens, and underscores'
});
}
const fullName = `${req.session.username}_${name}`;
// Check if devbench already exists
db.get('SELECT * FROM devbenches WHERE user_id = ? AND name = ?',
[req.session.userId, name], (err, existing) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
if (existing) {
return res.status(400).json({ error: 'DevBench with this name already exists' });
}
// Insert into database first
db.run('INSERT INTO devbenches (user_id, name, status) VALUES (?, ?, ?)',
[req.session.userId, name, 'creating'], function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Database error' });
}
const devbenchId = this.lastID;
console.log(`Created devbench record with ID: ${devbenchId}, full name: ${fullName}`);
// Start the provision script
setTimeout(() => {
executeProvisionScript('create', fullName, req.session.userId, devbenchId);
}, 1000); // Small delay to ensure WebSocket is ready
res.json({ success: true, devbenchId });
});
});
});
app.post('/delete-devbench/:id', requireAuth, (req, res) => {
const devbenchId = req.params.id;
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
[devbenchId, req.session.userId], (err, devbench) => {
if (err || !devbench) {
return res.status(404).json({ error: 'DevBench not found' });
}
if (devbench.actual_name) {
executeProvisionScript('delete', devbench.actual_name, req.session.userId, devbenchId);
}
db.run('DELETE FROM devbenches WHERE id = ?', [devbenchId], (err) => {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
res.json({ success: true });
});
});
});
app.post('/activate-devbench/:id', requireAuth, (req, res) => {
const devbenchId = req.params.id;
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
[devbenchId, req.session.userId], (err, devbench) => {
if (err || !devbench) {
return res.status(404).json({ error: 'DevBench not found' });
}
if (devbench.actual_name) {
executeProvisionScript('activate', devbench.actual_name, req.session.userId, devbenchId);
}
res.json({ success: true });
});
});
// Function to execute provision script
function executeProvisionScript(command, vmName, userId, devbenchId) {
console.log(`Executing provision script: ${command} ${vmName} for user ${userId}`);
const scriptPath = config.provision.scriptPath;
let output = '';
// Check if script exists
const fs = require('fs');
if (!fs.existsSync(scriptPath)) {
console.error(`Script not found: ${scriptPath}`);
broadcastToUser(userId, {
type: 'script_output',
devbenchId,
data: `Error: Script not found at ${scriptPath}\n`
});
// Update database to reflect error
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
return;
}
// Send initial message
broadcastToUser(userId, {
type: 'script_output',
devbenchId,
data: `Starting ${command} operation for ${vmName}...\n`
});
const child = spawn('bash', [scriptPath, command, vmName], {
cwd: process.cwd(),
env: process.env
});
child.stdout.on('data', (data) => {
const chunk = data.toString();
output += chunk;
console.log(`Script output: ${chunk.trim()}`);
// Broadcast real-time output to user
broadcastToUser(userId, {
type: 'script_output',
devbenchId,
data: chunk
});
});
child.stderr.on('data', (data) => {
const chunk = data.toString();
output += chunk;
console.log(`Script error: ${chunk.trim()}`);
broadcastToUser(userId, {
type: 'script_output',
devbenchId,
data: `ERROR: ${chunk}`
});
});
child.on('error', (error) => {
console.error(`Script execution error:`, error);
broadcastToUser(userId, {
type: 'script_output',
devbenchId,
data: `Execution Error: ${error.message}\n`
});
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
});
child.on('close', (code) => {
console.log(`Script finished with exit code: ${code}`);
if (command === 'create') {
if (code === 0) {
// Parse the output to extract VM name, SSH port, and VNC port
const vmNameMatch = output.match(/VM_NAME=(.+)/);
const sshPortMatch = output.match(/SSH_PORT=(\d+)/);
const vncPortMatch = output.match(/VNC_PORT=(\d+)/);
if (vmNameMatch && sshPortMatch && vncPortMatch) {
const actualName = vmNameMatch[1].trim();
const sshPort = sshPortMatch[1];
const vncPort = vncPortMatch[1];
// Generate SSH and VNC info
const sshInfo = sshPort;
const vncInfo = vncPort;
console.log(`Updating database: ${actualName}, SSH Port: ${sshPort}, VNC Port: ${vncPort}`);
db.run(`UPDATE devbenches SET
actual_name = ?, status = 'active',
ssh_info = ?, vnc_info = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[actualName, sshInfo, vncInfo, devbenchId], (err) => {
if (err) {
console.error('Database update error:', err);
} else {
console.log('Database updated successfully');
}
});
} else {
console.log('Could not parse VM info from output');
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
}
} else {
console.log('Script failed, updating status to error');
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
}
}
broadcastToUser(userId, {
type: 'script_complete',
devbenchId,
exitCode: code
});
});
}
// Status check endpoint
app.get('/check-status/:id', requireAuth, (req, res) => {
const devbenchId = req.params.id;
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
[devbenchId, req.session.userId], (err, devbench) => {
if (err || !devbench || !devbench.actual_name) {
return res.json({ status: 'unknown' });
}
const child = spawn('bash', [config.provision.scriptPath, 'status', devbench.actual_name]);
let output = '';
child.stdout.on('data', (data) => {
output += data.toString();
});
child.stderr.on('data', (data) => {
output += data.toString();
});
child.on('close', (code) => {
// Parse status from output - look for "active" or "inactive" in the output
let status = 'inactive';
if (output.includes('active')) {
status = 'active';
}
console.log(`Status check for ${devbench.actual_name}: ${status} (exit code: ${code})`);
db.run('UPDATE devbenches SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[status, devbenchId]);
res.json({ status });
});
});
});
// Periodic status check (every minute)
setInterval(() => {
db.all('SELECT * FROM devbenches WHERE actual_name IS NOT NULL', (err, devbenches) => {
if (err) return;
devbenches.forEach(devbench => {
const child = spawn('bash', [config.provision.scriptPath, 'status', devbench.actual_name]);
let output = '';
child.stdout.on('data', (data) => {
output += data.toString();
});
child.stderr.on('data', (data) => {
output += data.toString();
});
child.on('close', (code) => {
// Parse status from output - look for "active" in the output
let status = 'inactive';
if (output.includes('active')) {
status = 'active';
}
if (status !== devbench.status) {
console.log(`Status changed for ${devbench.actual_name}: ${devbench.status} -> ${status}`);
db.run('UPDATE devbenches SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[status, devbench.id]);
// Notify user of status change
broadcastToUser(devbench.user_id, {
type: 'status_update',
devbenchId: devbench.id,
status
});
}
});
});
});
}, config.provision.statusCheckInterval);
const PORT = config.port;
server.listen(PORT, () => {
console.log(`DevBench Manager running on port ${PORT}`);
console.log(`Database path: ${config.database.path}`);
console.log(`Provision script path: ${config.provision.scriptPath}`);
// Check if provision script exists
const fs = require('fs');
if (fs.existsSync(config.provision.scriptPath)) {
console.log('✓ Provision script found');
} else {
console.log('✗ Provision script NOT found');
}
});

View File

@@ -0,0 +1,31 @@
#!/bin/bash
echo "Starting DevBench Manager..."
# Check if Docker is available
if command -v docker &> /dev/null && command -v docker-compose &> /dev/null; then
echo "Using Docker Compose..."
docker-compose up -d
echo "DevBench Manager started with Docker!"
echo "Access it at: http://localhost:3001"
echo "Default admin login: admin / admin123"
else
echo "Docker not found, starting with Node.js..."
# Check if Node.js is available
if ! command -v node &> /dev/null; then
echo "Error: Node.js is not installed!"
echo "Please install Node.js 18+ or Docker to run this application."
exit 1
fi
# Install dependencies if node_modules doesn't exist
if [ ! -d "node_modules" ]; then
echo "Installing dependencies..."
npm install
fi
# Start the application
echo "Starting application..."
npm start
fi

View File

@@ -0,0 +1,212 @@
<%- include('layout', { body: `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-cogs"></i> Admin Dashboard</h2>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="fas fa-user-plus"></i> Add User
</button>
</div>
<!-- Users Section -->
<div class="card mb-4">
<div class="card-header">
<h5><i class="fas fa-users"></i> Users Management</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>DevBenches</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${users.map(user => `
<tr>
<td>${user.username}</td>
<td>${user.email}</td>
<td><span class="badge bg-info">${user.devbench_count}</span></td>
<td>${new Date(user.created_at).toLocaleDateString()}</td>
<td>
<button class="btn btn-sm btn-warning" onclick="resetPassword(${user.id})">
<i class="fas fa-key"></i> Reset Password
</button>
<button class="btn btn-sm btn-danger" onclick="deleteUser(${user.id})">
<i class="fas fa-trash"></i> Delete
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
<!-- DevBenches Section -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-server"></i> All DevBenches</h5>
<small class="text-light"><i class="fas fa-info-circle"></i> Default SSH/VNC Password: <strong>ASF</strong></small>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>User</th>
<th>DevBench Name</th>
<th>Actual Name</th>
<th>Status</th>
<th>SSH Port</th>
<th>VNC Port</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${devbenches.map(db => `
<tr>
<td>${db.username}</td>
<td>${db.name}</td>
<td>${db.actual_name || 'N/A'}</td>
<td>
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
${db.status}
</span>
</td>
<td>${db.ssh_info || 'N/A'}</td>
<td>${db.vnc_info || 'N/A'}</td>
<td>${new Date(db.created_at).toLocaleDateString()}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="adminDeleteDevbench(${db.id})">
<i class="fas fa-trash"></i> Delete
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
<!-- Add User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="addUserForm">
<div class="modal-body">
<div class="mb-3">
<label for="newUsername" class="form-label">Username</label>
<input type="text" class="form-control" id="newUsername" name="username" required
pattern="[a-zA-Z]+" title="Username must contain only letters">
<div class="form-text">Username must contain only letters (no spaces, numbers, or special characters)</div>
</div>
<div class="mb-3">
<label for="newEmail" class="form-label">Email</label>
<input type="email" class="form-control" id="newEmail" name="email" required>
</div>
<div class="mb-3">
<label for="newPassword" class="form-label">Initial Password</label>
<input type="password" class="form-control" id="newPassword" name="password" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">Add User</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch('/admin/add-user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(formData))
});
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error);
}
} catch (error) {
alert('Error adding user');
}
});
async function deleteUser(userId) {
if (confirm('Are you sure you want to delete this user and all their DevBenches?')) {
try {
const response = await fetch(\`/admin/delete-user/\${userId}\`, { method: 'POST' });
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error);
}
} catch (error) {
alert('Error deleting user');
}
}
}
async function resetPassword(userId) {
const newPassword = prompt('Enter new password:');
if (newPassword) {
try {
const response = await fetch(\`/admin/reset-password/\${userId}\`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ newPassword })
});
const result = await response.json();
if (result.success) {
alert('Password reset successfully');
} else {
alert(result.error);
}
} catch (error) {
alert('Error resetting password');
}
}
}
async function adminDeleteDevbench(devbenchId) {
if (confirm('Are you sure you want to delete this DevBench?')) {
try {
const response = await fetch(\`/delete-devbench/\${devbenchId}\`, { method: 'POST' });
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error);
}
} catch (error) {
alert('Error deleting DevBench');
}
}
}
</script>
`, username: typeof username !== 'undefined' ? username : 'admin' }) %>

View File

@@ -0,0 +1,208 @@
<%- include('layout', { body: `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-tachometer-alt"></i> My DevBenches</h2>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createDevbenchModal">
<i class="fas fa-plus"></i> Create DevBench
</button>
</div>
<div class="row">
${devbenches.map(db => `
<div class="col-md-6 col-lg-4 mb-4">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">${db.name}</h6>
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
${db.status}
</span>
</div>
<div class="card-body">
${db.actual_name ? `
<p><strong>VM Name:</strong> ${db.actual_name}</p>
${db.ssh_info || db.vnc_info ? `
<div class="connection-info">
<h6><i class="fas fa-plug"></i> Connection Info:</h6>
${db.ssh_info ? `
<div class="mb-3">
<strong><i class="fas fa-terminal"></i> SSH Port:</strong><br>
<code class="fs-5">${db.ssh_info}</code>
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.ssh_info}')">
<i class="fas fa-copy"></i>
</button>
</div>
` : ''}
${db.vnc_info ? `
<div class="mb-3">
<strong><i class="fas fa-desktop"></i> VNC Port:</strong><br>
<code class="fs-5">${db.vnc_info}</code>
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.vnc_info}')">
<i class="fas fa-copy"></i>
</button>
</div>
` : ''}
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle"></i> Need help connecting?
<a href="/help" class="alert-link">View setup guide</a>
</div>
</div>
` : ''}
` : `
<p class="text-muted">DevBench is being created...</p>
<div class="log-output" id="log-${db.id}"></div>
`}
<p class="text-muted mt-2">
<small>Created: ${new Date(db.created_at).toLocaleString()}</small>
</p>
</div>
<div class="card-footer">
<div class="btn-group w-100" role="group">
${db.status !== 'creating' ? `
<button class="btn btn-sm btn-primary" onclick="activateDevbench(${db.id})"
${db.status === 'active' ? 'disabled' : ''}>
<i class="fas fa-play"></i> Activate
</button>
<button class="btn btn-sm btn-info" onclick="checkStatus(${db.id})">
<i class="fas fa-sync"></i> Check Status
</button>
` : ''}
<button class="btn btn-sm btn-danger" onclick="deleteDevbench(${db.id})">
<i class="fas fa-trash"></i> Delete
</button>
</div>
</div>
</div>
</div>
`).join('')}
${devbenches.length === 0 ? `
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-server fa-3x text-muted mb-3"></i>
<h4 class="text-muted">No DevBenches Yet</h4>
<p class="text-muted">Create your first DevBench to get started!</p>
</div>
</div>
` : ''}
</div>
<!-- Create DevBench Modal -->
<div class="modal fade" id="createDevbenchModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create New DevBench</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="createDevbenchForm">
<div class="modal-body">
<div class="mb-3">
<label for="devbenchName" class="form-label">DevBench Name</label>
<input type="text" class="form-control" id="devbenchName" name="name" required
pattern="[a-zA-Z0-9_-]+" title="Only letters, numbers, hyphens, and underscores allowed">
<div class="form-text">
Only letters, numbers, hyphens (-), and underscores (_) are allowed.<br>
Full name will be: <strong>${username}_[your-name]</strong>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">Create DevBench</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.getElementById('createDevbenchForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch('/create-devbench', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(formData))
});
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error);
}
} catch (error) {
alert('Error creating DevBench');
}
});
async function deleteDevbench(devbenchId) {
if (confirm('Are you sure you want to delete this DevBench?')) {
try {
const response = await fetch(\`/delete-devbench/\${devbenchId}\`, { method: 'POST' });
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error);
}
} catch (error) {
alert('Error deleting DevBench');
}
}
}
async function activateDevbench(devbenchId) {
try {
const response = await fetch(\`/activate-devbench/\${devbenchId}\`, { method: 'POST' });
const result = await response.json();
if (result.success) {
alert('DevBench activation started');
} else {
alert(result.error);
}
} catch (error) {
alert('Error activating DevBench');
}
}
async function checkStatus(devbenchId) {
try {
const response = await fetch(\`/check-status/\${devbenchId}\`);
const result = await response.json();
const statusElement = document.getElementById(\`status-\${devbenchId}\`);
if (statusElement) {
statusElement.className = \`badge bg-\${result.status === 'active' ? 'success' : 'danger'}\`;
statusElement.textContent = result.status;
}
} catch (error) {
alert('Error checking status');
}
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
// Show a temporary success message
const toast = document.createElement('div');
toast.className = 'alert alert-success position-fixed';
toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; opacity: 0.9;';
toast.innerHTML = '<i class="fas fa-check"></i> Copied to clipboard!';
document.body.appendChild(toast);
setTimeout(() => {
document.body.removeChild(toast);
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
alert('Failed to copy to clipboard');
});
}
</script>
`, username }) %>

View File

@@ -0,0 +1,125 @@
<%- include('layout', { body: `
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-question-circle"></i> Help - How to Use DevBench Manager</h2>
<a href="/dashboard" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> Back to Dashboard
</a>
</div>
<div class="card mb-4">
<div class="card-header bg-info text-white">
<h5><i class="fas fa-info-circle"></i> SSH Configuration Tool</h5>
</div>
<div class="card-body">
<p class="lead">Follow these steps to configure SSH access to your VM:</p>
<ol class="help-steps">
<li>
<strong>Download the VM SSH Config Tool</strong>
<p>Download the configuration tool from here:</p>
<a href="/downloads/db_vm_ssh_config_manager.exe" class="btn btn-success mb-2" download>
<i class="fas fa-download"></i> Download SSH Config Manager
</a>
</li>
<li>
<strong>Open the Tool</strong>
<p>Run the <code>db_vm_ssh_config_manager.exe</code> application.</p>
</li>
<li>
<strong>Add Friendly Name</strong>
<p>In the "Host" field, enter a friendly name for your VM (e.g., <code>vm-test1</code>).</p>
</li>
<li>
<strong>Add SSH Port</strong>
<p>Enter the SSH port number shown in your DevBench connection info (e.g., <code>6004</code>).</p>
</li>
<li>
<strong>Add Username</strong>
<p>Enter the username: <code>asf_user</code></p>
</li>
<li>
<strong>Keep Jump Proxy</strong>
<p>Keep the jump proxy as: <code>asf-jump</code></p>
</li>
<li>
<strong>Press Add VM</strong>
<p>Click the "Add VM" button to save the configuration.</p>
</li>
<li>
<strong>Select VM Host and Show Command</strong>
<p>In the "Select VM Host" dropdown, choose your VM name and click "Show Commands".</p>
</li>
<li>
<strong>Access Your VM</strong>
<p>You will now see the SSH command to access your VM. Copy and use it in your terminal.</p>
</li>
</ol>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5><i class="fas fa-terminal"></i> Connection Information</h5>
</div>
<div class="card-body">
<h6>SSH Connection:</h6>
<p>After configuring the SSH tool, you can connect using:</p>
<pre class="bg-light p-3 rounded"><code>ssh [your-vm-name]</code></pre>
<h6 class="mt-4">VNC Connection:</h6>
<p>For graphical access, use your VNC client with the port shown in your DevBench info.</p>
<p><strong>Default Password:</strong> <code>ASF</code></p>
</div>
</div>
<div class="card">
<div class="card-header bg-warning text-dark">
<h5><i class="fas fa-exclamation-triangle"></i> Important Notes</h5>
</div>
<div class="card-body">
<ul>
<li>The default password for both SSH and VNC is: <strong>ASF</strong></li>
<li>Make sure to keep your SSH config tool updated with the correct ports</li>
<li>Each DevBench has unique SSH and VNC ports</li>
<li>Contact your administrator if you encounter any issues</li>
</ul>
</div>
</div>
<style>
.help-steps {
font-size: 1.1rem;
line-height: 1.8;
}
.help-steps li {
margin-bottom: 1.5rem;
}
.help-steps strong {
color: #0d6efd;
display: block;
margin-bottom: 0.5rem;
}
.help-steps p {
margin-left: 1rem;
color: #666;
}
.help-steps code {
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
color: #d63384;
}
</style>
`, username }) %>

View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevBench Manager</title>
<link rel="icon" type="image/png" href="/images/tbm-icon.png">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<img src="/images/tbm-icon.png" alt="TBM" class="tbm-icon me-2" style="height: 32px; width: 32px;">
<img src="/images/nabd-logo-white.svg" alt="NABD Solutions" class="company-logo">
DevBench Manager
</a>
<div class="navbar-nav ms-auto">
<% if (typeof username !== 'undefined') { %>
<a class="nav-link" href="/help" title="Help">
<i class="fas fa-question-circle"></i> Help
</a>
<span class="navbar-text me-3">Welcome, <%= username %></span>
<a class="nav-link" href="/logout">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
<% } %>
</div>
</div>
</nav>
<div class="container mt-4">
<%- body %>
</div>
<!-- Theme Toggle Button -->
<button class="theme-toggle" id="themeToggle" title="Toggle Dark/Light Theme">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Theme Toggle Functionality
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const body = document.body;
// Load saved theme preference
const savedTheme = localStorage.getItem('theme') || 'light';
if (savedTheme === 'dark') {
body.classList.add('dark-theme');
themeIcon.classList.remove('fa-moon');
themeIcon.classList.add('fa-sun');
}
// Toggle theme
themeToggle.addEventListener('click', () => {
body.classList.toggle('dark-theme');
if (body.classList.contains('dark-theme')) {
themeIcon.classList.remove('fa-moon');
themeIcon.classList.add('fa-sun');
localStorage.setItem('theme', 'dark');
} else {
themeIcon.classList.remove('fa-sun');
themeIcon.classList.add('fa-moon');
localStorage.setItem('theme', 'light');
}
});
</script>
<script>
// WebSocket connection for real-time updates
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${window.location.host}`);
ws.onopen = function() {
console.log('WebSocket connected');
// Register this connection with user ID if available
<% if (typeof username !== 'undefined') { %>
// Get user ID from session (we'll need to pass this from server)
fetch('/api/user-info')
.then(response => response.json())
.then(data => {
if (data.userId) {
ws.send(JSON.stringify({
type: 'register',
userId: data.userId
}));
console.log('Registered WebSocket for user:', data.userId);
}
})
.catch(error => console.error('Error getting user info:', error));
<% } %>
};
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
console.log('WebSocket message received:', message);
handleWebSocketMessage(message);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
ws.onclose = function() {
console.log('WebSocket disconnected');
};
function handleWebSocketMessage(message) {
if (message.type === 'script_output') {
const logElement = document.getElementById(`log-${message.devbenchId}`);
if (logElement) {
logElement.textContent += message.data;
logElement.scrollTop = logElement.scrollHeight;
}
} else if (message.type === 'script_complete') {
console.log('Script completed with exit code:', message.exitCode);
setTimeout(() => {
location.reload();
}, 3000);
} else if (message.type === 'status_update') {
const statusElement = document.getElementById(`status-${message.devbenchId}`);
if (statusElement) {
statusElement.className = `badge bg-${message.status === 'active' ? 'success' : 'danger'}`;
statusElement.textContent = message.status;
}
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<%- include('layout', { body: `
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card shadow login-card">
<div class="card-header bg-primary text-white text-center">
<img src="/images/nabd-logo.svg" alt="NABD Solutions" style="height: 40px; margin-bottom: 10px;">
<h4><i class="fas fa-sign-in-alt"></i> Login</h4>
</div>
<div class="card-body">
${error ? `<div class="alert alert-danger">${error}</div>` : ''}
<form method="POST" action="/login">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-sign-in-alt"></i> Login
</button>
</form>
</div>
<div class="card-footer text-center text-muted">
<small>DevBench Manager v1.0</small>
</div>
</div>
</div>
</div>
` }) %>