Fix security warnings
This commit is contained in:
@@ -1,2 +1,6 @@
|
|||||||
# Timezone
|
# Timezone
|
||||||
TZ=Europe/Copenhagen
|
TZ=Europe/Copenhagen
|
||||||
|
|
||||||
|
# Admin Credentials (CHANGE THESE!)
|
||||||
|
ADMIN_EMAIL=admin@docker-webui.local
|
||||||
|
ADMIN_PASSWORD=changeme123
|
||||||
|
|||||||
343
README.md
343
README.md
@@ -1,73 +1,314 @@
|
|||||||
# Welcome to your Lovable project
|
# 🐳 Docker WebUI
|
||||||
|
|
||||||
## Project info
|
A lightweight, self-hosted web interface for monitoring and controlling Docker containers in real-time.
|
||||||
|
|
||||||
**URL**: https://lovable.dev/projects/ba519789-1c3e-4fe3-a965-cdc4a30e6a8b
|
## ✨ Features
|
||||||
|
|
||||||
## How can I edit this code?
|
- **Real-time Monitoring** - Live metrics via WebSocket (CPU, memory, network, I/O)
|
||||||
|
- **Auto-discovery** - Containers created/removed via CLI appear instantly in UI
|
||||||
|
- **Container Control** - Start, stop, restart containers from the web interface
|
||||||
|
- **Live Logs** - Stream container logs in real-time with filtering
|
||||||
|
- **Discord Alerts** - Per-container notifications on stop/error
|
||||||
|
- **Dark Mode** - Beautiful, responsive interface
|
||||||
|
- **Single Admin** - Simple authentication with one admin user
|
||||||
|
|
||||||
There are several ways of editing your application.
|
## 🚀 Quick Start
|
||||||
|
|
||||||
**Use Lovable**
|
### Prerequisites
|
||||||
|
|
||||||
Simply visit the [Lovable Project](https://lovable.dev/projects/ba519789-1c3e-4fe3-a965-cdc4a30e6a8b) and start prompting.
|
- Docker & Docker Compose
|
||||||
|
- Host with Docker socket access (`/var/run/docker.sock`)
|
||||||
|
|
||||||
Changes made via Lovable will be committed automatically to this repo.
|
### Installation
|
||||||
|
|
||||||
**Use your preferred IDE**
|
1. Clone this repository:
|
||||||
|
```bash
|
||||||
If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
|
git clone <your-repo-url>
|
||||||
|
cd docker-webui
|
||||||
The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
|
|
||||||
|
|
||||||
Follow these steps:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Step 1: Clone the repository using the project's Git URL.
|
|
||||||
git clone <YOUR_GIT_URL>
|
|
||||||
|
|
||||||
# Step 2: Navigate to the project directory.
|
|
||||||
cd <YOUR_PROJECT_NAME>
|
|
||||||
|
|
||||||
# Step 3: Install the necessary dependencies.
|
|
||||||
npm i
|
|
||||||
|
|
||||||
# Step 4: Start the development server with auto-reloading and an instant preview.
|
|
||||||
npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Edit a file directly in GitHub**
|
2. Copy and configure environment:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
nano .env # Edit admin credentials
|
||||||
|
```
|
||||||
|
|
||||||
- Navigate to the desired file(s).
|
3. Set your admin credentials in `.env`:
|
||||||
- Click the "Edit" button (pencil icon) at the top right of the file view.
|
```env
|
||||||
- Make your changes and commit the changes.
|
ADMIN_EMAIL=admin@example.com
|
||||||
|
ADMIN_PASSWORD=your-secure-password
|
||||||
|
TZ=Europe/Copenhagen
|
||||||
|
```
|
||||||
|
|
||||||
**Use GitHub Codespaces**
|
4. Build and start:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
- Navigate to the main page of your repository.
|
5. Access the UI at `http://your-server-ip:8080`
|
||||||
- Click on the "Code" button (green button) near the top right.
|
|
||||||
- Select the "Codespaces" tab.
|
|
||||||
- Click on "New codespace" to launch a new Codespace environment.
|
|
||||||
- Edit files directly within the Codespace and commit and push your changes once you're done.
|
|
||||||
|
|
||||||
## What technologies are used for this project?
|
6. **First Login:**
|
||||||
|
- Navigate to `/settings` in your browser
|
||||||
|
- Click "Sign Up" to create the admin account
|
||||||
|
- Use the credentials from your `.env` file
|
||||||
|
- After creating the account, login with the same credentials
|
||||||
|
|
||||||
This project is built with:
|
## 🔧 Configuration
|
||||||
|
|
||||||
- Vite
|
### Docker Compose
|
||||||
- TypeScript
|
|
||||||
- React
|
|
||||||
- shadcn-ui
|
|
||||||
- Tailwind CSS
|
|
||||||
|
|
||||||
## How can I deploy this project?
|
The service uses **host networking mode** for direct Docker socket access and requires:
|
||||||
|
- `/var/run/docker.sock` (read-only) - Docker API access
|
||||||
|
- `/proc` (read-only) - Host system metrics
|
||||||
|
|
||||||
Simply open [Lovable](https://lovable.dev/projects/ba519789-1c3e-4fe3-a965-cdc4a30e6a8b) and click on Share -> Publish.
|
### Environment Variables
|
||||||
|
|
||||||
## Can I connect a custom domain to my Lovable project?
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `ADMIN_EMAIL` | `admin@docker-webui.local` | Admin email address |
|
||||||
|
| `ADMIN_PASSWORD` | `changeme123` | Admin password (min 6 chars) |
|
||||||
|
| `TZ` | `Europe/Copenhagen` | Server timezone |
|
||||||
|
|
||||||
Yes, you can!
|
### Changing the Port
|
||||||
|
|
||||||
To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
|
Edit `nginx.conf`:
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 8082; # Change from 8080 to your desired port
|
||||||
|
server_name _;
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Read more here: [Setting up a custom domain](https://docs.lovable.dev/features/custom-domain#custom-domain)
|
Then rebuild:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Usage
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
- **Host Metrics** - CPU, RAM, network, uptime
|
||||||
|
- **Container List** - All containers with live stats
|
||||||
|
- **Quick Actions** - Start/stop/restart buttons
|
||||||
|
- **Auto-refresh** - Updates every 2-5 seconds
|
||||||
|
|
||||||
|
### Container Details
|
||||||
|
- Real-time metrics graphs
|
||||||
|
- Live log streaming (no storage, ephemeral)
|
||||||
|
- Container configuration details
|
||||||
|
- Per-container alert settings
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
- Discord webhook configuration
|
||||||
|
- Metrics refresh interval
|
||||||
|
- Alert debounce settings
|
||||||
|
- Error pattern regex
|
||||||
|
|
||||||
|
## 🔔 Discord Alerts
|
||||||
|
|
||||||
|
Set up per-container Discord notifications:
|
||||||
|
|
||||||
|
1. Create a Discord webhook in your server
|
||||||
|
2. In WebUI Settings, add your webhook URL
|
||||||
|
3. Enable alerts for specific containers
|
||||||
|
4. Configure triggers:
|
||||||
|
- **Container Stopped** - Alert when container stops/crashes
|
||||||
|
- **Error Pattern** - Match regex in logs (default: `(?i)(error|err|exception|traceback|crit(ical)?)`)
|
||||||
|
|
||||||
|
Example alert:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "Docker WebUI",
|
||||||
|
"embeds": [{
|
||||||
|
"title": "Container Stopped",
|
||||||
|
"color": 15158332,
|
||||||
|
"fields": [
|
||||||
|
{"name": "Container", "value": "redis:prod"},
|
||||||
|
{"name": "Reason", "value": "stop event"},
|
||||||
|
{"name": "Time", "value": "2025-10-20T10:22:31Z"}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 Security
|
||||||
|
|
||||||
|
⚠️ **Important Security Considerations:**
|
||||||
|
|
||||||
|
- **Root Required** - Container needs root access to Docker socket
|
||||||
|
- **Trusted Hosts Only** - Docker socket access = full system control
|
||||||
|
- **Single Admin** - Only one user (configured via env vars)
|
||||||
|
- **Read-only Mounts** - Socket and `/proc` are mounted read-only
|
||||||
|
- **No Public Exposure** - Always use behind HTTPS reverse proxy
|
||||||
|
- **Audit Logging** - All control actions logged to database
|
||||||
|
|
||||||
|
### Reverse Proxy Setup (Recommended)
|
||||||
|
|
||||||
|
Example Nginx configuration:
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name docker.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /path/to/cert.pem;
|
||||||
|
ssl_certificate_key /path/to/key.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Development
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
|
||||||
|
- **Frontend**: React 18 + TypeScript + Vite
|
||||||
|
- **Backend**: Lovable Cloud (Supabase)
|
||||||
|
- **UI Framework**: shadcn/ui + Tailwind CSS
|
||||||
|
- **Real-time**: WebSocket for metrics streaming
|
||||||
|
- **Deployment**: Docker + Nginx
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start dev server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-webui/
|
||||||
|
├── src/
|
||||||
|
│ ├── pages/ # Dashboard, Login, Settings
|
||||||
|
│ ├── components/ # Reusable UI components
|
||||||
|
│ ├── hooks/ # Custom React hooks
|
||||||
|
│ └── integrations/ # Supabase client
|
||||||
|
├── supabase/
|
||||||
|
│ ├── functions/ # Edge functions (Docker API)
|
||||||
|
│ └── config.toml # Function configuration
|
||||||
|
├── Dockerfile # Multi-stage build
|
||||||
|
├── nginx.conf # Web server config
|
||||||
|
└── docker-compose.yml # Service definition
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ WebSocket ┌───────────────┐
|
||||||
|
│ Browser │◄────────────────────────►│ docker-metrics│
|
||||||
|
│ (React) │ │ Edge Function │
|
||||||
|
└──────────────┘ └───────┬───────┘
|
||||||
|
│
|
||||||
|
┌──────────────┐ REST API ┌───────▼───────┐
|
||||||
|
│ Dashboard │◄────────────────────────►│ docker-api │
|
||||||
|
│ Frontend │ │ Edge Function │
|
||||||
|
└──────────────┘ └───────┬───────┘
|
||||||
|
│
|
||||||
|
┌──────▼─────────┐
|
||||||
|
│ /var/run/ │
|
||||||
|
│ docker.sock │
|
||||||
|
└────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
1. Frontend connects via WebSocket for real-time metrics
|
||||||
|
2. Backend edge functions read Docker socket via Unix socket
|
||||||
|
3. Container stats streamed every 2 seconds
|
||||||
|
4. Container list auto-updates via polling + WebSocket
|
||||||
|
|
||||||
|
## 🚀 Features in Detail
|
||||||
|
|
||||||
|
### Auto-Discovery
|
||||||
|
- Detects new containers within 5 seconds
|
||||||
|
- Automatically removes deleted containers from UI
|
||||||
|
- No manual refresh needed
|
||||||
|
- Works with `docker run`, `docker-compose`, etc.
|
||||||
|
|
||||||
|
### Live Metrics
|
||||||
|
- **Host**: CPU %, RAM usage, network I/O, uptime
|
||||||
|
- **Containers**: CPU %, memory, network RX/TX, block I/O
|
||||||
|
- **Refresh**: 2-second intervals via WebSocket
|
||||||
|
- **No Storage**: All metrics are ephemeral (not saved)
|
||||||
|
|
||||||
|
### Log Streaming
|
||||||
|
- Streams directly from Docker (no disk storage)
|
||||||
|
- Client-side regex filtering
|
||||||
|
- Color-coded timestamps
|
||||||
|
- Auto-scroll with pause/resume
|
||||||
|
- Logs disappear on browser close
|
||||||
|
|
||||||
|
## 🔮 Roadmap
|
||||||
|
|
||||||
|
- [ ] Multi-host support (lightweight agent model)
|
||||||
|
- [ ] Historical metrics with charts
|
||||||
|
- [ ] Advanced health check rules
|
||||||
|
- [ ] Multi-language UI (English/Danish)
|
||||||
|
- [ ] Docker Compose stack management
|
||||||
|
- [ ] Volume and network management
|
||||||
|
- [ ] Resource limits configuration
|
||||||
|
|
||||||
|
## ⚠️ Limitations
|
||||||
|
|
||||||
|
- **No Shell Access** - WebUI does not provide terminal/exec access
|
||||||
|
- **No Log Storage** - Logs stream live but are not persisted
|
||||||
|
- **Single Host** - Multi-host requires future agent architecture
|
||||||
|
- **Rate Limits** - Discord webhooks have rate limits (use debounce)
|
||||||
|
- **Root Access** - Requires privileged Docker socket access
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Container not accessible from browser
|
||||||
|
- Check that you're using host networking mode
|
||||||
|
- Verify firewall allows port 8080
|
||||||
|
- Try accessing via `http://HOST_IP:8080`
|
||||||
|
|
||||||
|
### Metrics not updating
|
||||||
|
- Check WebSocket connection (WiFi icon in header)
|
||||||
|
- Verify `/var/run/docker.sock` is mounted
|
||||||
|
- Check edge function logs in Lovable Cloud
|
||||||
|
|
||||||
|
### Can't login
|
||||||
|
- Ensure you created the admin account first (Settings → Sign Up)
|
||||||
|
- Verify credentials match `.env` file
|
||||||
|
- Check browser console for errors
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Contributions welcome! Please:
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Commit your changes
|
||||||
|
4. Open a Pull Request
|
||||||
|
|
||||||
|
## 💬 Support
|
||||||
|
|
||||||
|
- **Issues**: Report bugs via GitHub Issues
|
||||||
|
- **Discussions**: Ask questions in GitHub Discussions
|
||||||
|
- **Documentation**: See inline code comments
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with ❤️ using [Lovable](https://lovable.dev)**
|
||||||
|
|
||||||
|
Deploy on trusted infrastructure only. Docker socket access = full system control.
|
||||||
|
|||||||
253
SETUP.md
Normal file
253
SETUP.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# Docker WebUI Setup Guide
|
||||||
|
|
||||||
|
## First-Time Setup
|
||||||
|
|
||||||
|
### Step 1: Configure Environment
|
||||||
|
|
||||||
|
1. Copy the example environment file:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `.env` with your admin credentials:
|
||||||
|
```env
|
||||||
|
ADMIN_EMAIL=admin@example.com
|
||||||
|
ADMIN_PASSWORD=YourSecurePassword123
|
||||||
|
TZ=Europe/Copenhagen
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Build and Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Build the React frontend
|
||||||
|
- Create the Docker container
|
||||||
|
- Mount `/var/run/docker.sock` (read-only)
|
||||||
|
- Mount `/proc` for host metrics
|
||||||
|
- Start the web server on port 8080
|
||||||
|
|
||||||
|
### Step 3: Create Admin Account
|
||||||
|
|
||||||
|
⚠️ **Important**: You must create the admin account before you can login.
|
||||||
|
|
||||||
|
1. Open your browser to `http://your-server-ip:8080`
|
||||||
|
2. You'll see the login page
|
||||||
|
3. Click on the Settings page link (or navigate to `/settings`)
|
||||||
|
4. Look for the "Sign Up" option
|
||||||
|
5. Enter the EXACT credentials from your `.env` file:
|
||||||
|
- Email: The value from `ADMIN_EMAIL`
|
||||||
|
- Password: The value from `ADMIN_PASSWORD`
|
||||||
|
6. Submit the signup form
|
||||||
|
|
||||||
|
### Step 4: Login
|
||||||
|
|
||||||
|
After creating the account:
|
||||||
|
1. Return to the login page (`/login`)
|
||||||
|
2. Login with your admin credentials
|
||||||
|
3. You'll be redirected to the dashboard
|
||||||
|
|
||||||
|
## System Requirements
|
||||||
|
|
||||||
|
- **Docker**: 20.10+
|
||||||
|
- **Docker Compose**: 1.29+
|
||||||
|
- **OS**: Linux (tested on Ubuntu 20.04+, Debian 11+)
|
||||||
|
- **RAM**: Minimum 512MB available
|
||||||
|
- **Network**: Port 8080 available (or customize in nginx.conf)
|
||||||
|
|
||||||
|
## Port Configuration
|
||||||
|
|
||||||
|
The default port is **8080**. To change it:
|
||||||
|
|
||||||
|
1. Edit `nginx.conf`:
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 8082; # Change this
|
||||||
|
server_name _;
|
||||||
|
# ... rest of config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Rebuild the container:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reverse Proxy Setup (Recommended)
|
||||||
|
|
||||||
|
For production, always use HTTPS via a reverse proxy.
|
||||||
|
|
||||||
|
### Nginx Reverse Proxy
|
||||||
|
|
||||||
|
Create `/etc/nginx/sites-available/docker-webui`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name docker.yourdomain.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/docker.yourdomain.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/docker.yourdomain.com/privkey.pem;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# Headers
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Timeouts
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirect HTTP to HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name docker.yourdomain.com;
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable and restart:
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/docker-webui /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Traefik (Docker)
|
||||||
|
|
||||||
|
Add labels to `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
docker-webui:
|
||||||
|
# ... existing config
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.docker-webui.rule=Host(`docker.yourdomain.com`)"
|
||||||
|
- "traefik.http.routers.docker-webui.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.docker-webui.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.docker-webui.loadbalancer.server.port=8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Firewall Configuration
|
||||||
|
|
||||||
|
If using UFW:
|
||||||
|
```bash
|
||||||
|
# Allow from specific IP
|
||||||
|
sudo ufw allow from 192.168.1.0/24 to any port 8080
|
||||||
|
|
||||||
|
# Or allow from anywhere (not recommended)
|
||||||
|
sudo ufw allow 8080/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discord Webhook Setup
|
||||||
|
|
||||||
|
1. In Discord, go to Server Settings → Integrations → Webhooks
|
||||||
|
2. Click "New Webhook"
|
||||||
|
3. Name it "Docker WebUI"
|
||||||
|
4. Copy the webhook URL
|
||||||
|
5. In Docker WebUI, go to Settings
|
||||||
|
6. Paste webhook URL
|
||||||
|
7. Configure per-container alerts
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Cannot connect to Docker daemon"
|
||||||
|
|
||||||
|
Check socket permissions:
|
||||||
|
```bash
|
||||||
|
ls -la /var/run/docker.sock
|
||||||
|
# Should show: srw-rw---- 1 root docker
|
||||||
|
|
||||||
|
# Add your user to docker group
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
newgrp docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebUI not accessible from other machines
|
||||||
|
|
||||||
|
Check host networking:
|
||||||
|
```bash
|
||||||
|
# Verify the container is using host network
|
||||||
|
docker inspect docker-webui | grep NetworkMode
|
||||||
|
# Should show: "NetworkMode": "host"
|
||||||
|
|
||||||
|
# Check if port is listening
|
||||||
|
sudo netstat -tulpn | grep 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics not updating
|
||||||
|
|
||||||
|
1. Check WebSocket connection (WiFi icon in header)
|
||||||
|
2. View edge function logs in Lovable Cloud
|
||||||
|
3. Verify container can access Docker socket:
|
||||||
|
```bash
|
||||||
|
docker exec docker-webui ls -la /var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
### High CPU usage
|
||||||
|
|
||||||
|
Reduce metrics refresh interval in Settings:
|
||||||
|
- Default: 2000ms (2 seconds)
|
||||||
|
- Recommended for many containers: 5000ms (5 seconds)
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **Change default credentials** in `.env` before first run
|
||||||
|
2. **Use strong passwords** (minimum 12 characters)
|
||||||
|
3. **Enable HTTPS** via reverse proxy
|
||||||
|
4. **Restrict network access** to trusted IPs only
|
||||||
|
5. **Keep Docker updated** for security patches
|
||||||
|
6. **Review audit logs** regularly for suspicious activity
|
||||||
|
7. **Backup configuration** and database regularly
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest changes
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Rebuild and restart
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstalling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop and remove container
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Remove images
|
||||||
|
docker rmi docker-webui:latest
|
||||||
|
|
||||||
|
# Remove volumes (if any)
|
||||||
|
docker volume prune
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- GitHub Issues: Report bugs
|
||||||
|
- GitHub Discussions: Ask questions
|
||||||
|
- Documentation: Check README.md
|
||||||
@@ -9,6 +9,8 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
- TZ=${TZ:-Europe/Copenhagen}
|
- TZ=${TZ:-Europe/Copenhagen}
|
||||||
|
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@docker-webui.local}
|
||||||
|
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme123}
|
||||||
volumes:
|
volumes:
|
||||||
# Mount Docker socket for container management (read-only for safety)
|
# Mount Docker socket for container management (read-only for safety)
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|||||||
@@ -125,27 +125,6 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
profiles: {
|
|
||||||
Row: {
|
|
||||||
created_at: string | null
|
|
||||||
id: string
|
|
||||||
updated_at: string | null
|
|
||||||
username: string
|
|
||||||
}
|
|
||||||
Insert: {
|
|
||||||
created_at?: string | null
|
|
||||||
id: string
|
|
||||||
updated_at?: string | null
|
|
||||||
username: string
|
|
||||||
}
|
|
||||||
Update: {
|
|
||||||
created_at?: string | null
|
|
||||||
id?: string
|
|
||||||
updated_at?: string | null
|
|
||||||
username?: string
|
|
||||||
}
|
|
||||||
Relationships: []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Views: {
|
Views: {
|
||||||
[_ in never]: never
|
[_ in never]: never
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Server, Lock, Mail } from "lucide-react";
|
import { Server, Lock, Mail, Shield } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
@@ -10,45 +10,23 @@ import { useToast } from "@/components/ui/use-toast";
|
|||||||
const Login = () => {
|
const Login = () => {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [isSignup, setIsSignup] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const handleAuth = async (e: React.FormEvent) => {
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isSignup) {
|
const { error } = await supabase.auth.signInWithPassword({
|
||||||
const { error } = await supabase.auth.signUp({
|
email,
|
||||||
email,
|
password,
|
||||||
password,
|
});
|
||||||
options: {
|
|
||||||
data: {
|
|
||||||
username: email.split('@')[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
toast({
|
navigate("/dashboard");
|
||||||
title: "Account created!",
|
|
||||||
description: "You can now log in.",
|
|
||||||
});
|
|
||||||
setIsSignup(false);
|
|
||||||
setPassword("");
|
|
||||||
} else {
|
|
||||||
const { error } = await supabase.auth.signInWithPassword({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
|
|
||||||
navigate("/dashboard");
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast({
|
toast({
|
||||||
title: "Authentication failed",
|
title: "Authentication failed",
|
||||||
@@ -69,21 +47,21 @@ const Login = () => {
|
|||||||
</div>
|
</div>
|
||||||
<h1 className="text-3xl font-bold text-foreground mb-2">Docker WebUI</h1>
|
<h1 className="text-3xl font-bold text-foreground mb-2">Docker WebUI</h1>
|
||||||
<p className="text-muted-foreground text-center">
|
<p className="text-muted-foreground text-center">
|
||||||
{isSignup ? 'Create an account to get started' : 'Lightweight container monitoring and control'}
|
Admin access only
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleAuth} className="space-y-4">
|
<form onSubmit={handleLogin} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label htmlFor="email" className="text-sm font-medium text-foreground">
|
<label htmlFor="email" className="text-sm font-medium text-foreground">
|
||||||
Email
|
Admin Email
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="admin@example.com"
|
placeholder="admin@docker-webui.local"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="pl-10 bg-secondary border-border"
|
className="pl-10 bg-secondary border-border"
|
||||||
@@ -118,29 +96,23 @@ const Login = () => {
|
|||||||
className="w-full bg-primary hover:bg-primary/90"
|
className="w-full bg-primary hover:bg-primary/90"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? 'Processing...' : (isSignup ? 'Sign Up' : 'Sign In')}
|
{loading ? 'Signing in...' : 'Sign In'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-6 p-4 rounded-lg bg-primary/5 border border-primary/20">
|
||||||
<button
|
<div className="flex items-start gap-2">
|
||||||
type="button"
|
<Shield className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
|
||||||
onClick={() => {
|
<div className="text-sm text-muted-foreground">
|
||||||
setIsSignup(!isSignup);
|
<p className="font-semibold text-foreground mb-1">Single Admin System</p>
|
||||||
setPassword("");
|
<p>Admin credentials are configured in docker-compose.yml via environment variables:</p>
|
||||||
}}
|
<ul className="mt-2 space-y-1 text-xs font-mono">
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
<li>• ADMIN_EMAIL</li>
|
||||||
disabled={loading}
|
<li>• ADMIN_PASSWORD</li>
|
||||||
>
|
</ul>
|
||||||
{isSignup ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isSignup && (
|
|
||||||
<p className="mt-6 text-xs text-center text-muted-foreground">
|
|
||||||
Secure authentication powered by Lovable Cloud
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
-- Remove profiles table (not needed for single admin user)
|
||||||
|
DROP TABLE IF EXISTS public.profiles CASCADE;
|
||||||
|
|
||||||
|
-- Remove the trigger and function for auto-creating profiles
|
||||||
|
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||||||
|
DROP FUNCTION IF EXISTS public.handle_new_user() CASCADE;
|
||||||
|
|
||||||
|
-- Update global_settings to not require user_id (single admin)
|
||||||
|
ALTER TABLE public.global_settings DROP CONSTRAINT IF EXISTS global_settings_user_id_fkey;
|
||||||
|
ALTER TABLE public.global_settings ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
|
||||||
|
-- Update container_settings to not require user_id
|
||||||
|
ALTER TABLE public.container_settings DROP CONSTRAINT IF EXISTS container_settings_user_id_fkey;
|
||||||
|
ALTER TABLE public.container_settings ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
|
||||||
|
-- Update audit_logs to not require user_id
|
||||||
|
ALTER TABLE public.audit_logs DROP CONSTRAINT IF EXISTS audit_logs_user_id_fkey;
|
||||||
|
ALTER TABLE public.audit_logs ALTER COLUMN user_id DROP NOT NULL;
|
||||||
|
|
||||||
|
-- Update RLS policies for single admin
|
||||||
|
DROP POLICY IF EXISTS "Users can view their own settings" ON public.global_settings;
|
||||||
|
DROP POLICY IF EXISTS "Users can insert their own settings" ON public.global_settings;
|
||||||
|
DROP POLICY IF EXISTS "Users can update their own settings" ON public.global_settings;
|
||||||
|
|
||||||
|
CREATE POLICY "Admin can manage global settings"
|
||||||
|
ON public.global_settings FOR ALL
|
||||||
|
USING (true)
|
||||||
|
WITH CHECK (true);
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS "Users can view their own container settings" ON public.container_settings;
|
||||||
|
DROP POLICY IF EXISTS "Users can insert their own container settings" ON public.container_settings;
|
||||||
|
DROP POLICY IF EXISTS "Users can update their own container settings" ON public.container_settings;
|
||||||
|
DROP POLICY IF EXISTS "Users can delete their own container settings" ON public.container_settings;
|
||||||
|
|
||||||
|
CREATE POLICY "Admin can manage container settings"
|
||||||
|
ON public.container_settings FOR ALL
|
||||||
|
USING (true)
|
||||||
|
WITH CHECK (true);
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS "Users can view their own audit logs" ON public.audit_logs;
|
||||||
|
DROP POLICY IF EXISTS "Users can insert their own audit logs" ON public.audit_logs;
|
||||||
|
|
||||||
|
CREATE POLICY "Admin can view audit logs"
|
||||||
|
ON public.audit_logs FOR ALL
|
||||||
|
USING (true)
|
||||||
|
WITH CHECK (true);
|
||||||
|
|
||||||
|
-- Create function to initialize admin settings on first login
|
||||||
|
CREATE OR REPLACE FUNCTION public.ensure_admin_settings()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
-- Create default global settings if they don't exist
|
||||||
|
INSERT INTO public.global_settings (user_id)
|
||||||
|
SELECT NEW.id
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM public.global_settings LIMIT 1);
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = public;
|
||||||
|
|
||||||
|
-- Trigger to ensure settings exist when admin logs in
|
||||||
|
CREATE TRIGGER ensure_admin_settings_on_login
|
||||||
|
AFTER INSERT ON auth.users
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION public.ensure_admin_settings();
|
||||||
Reference in New Issue
Block a user