Add in-app notifications for new panel version availability
- Add notify_update_available() method to NotificationService - Add translations for update notifications (ru/en) - Add 'update' type to default notification types - Add check_for_panel_updates() background task (runs every 6 hours) - Notifications include link to settings page for easy update access - Prevent duplicate notifications for same version
This commit is contained in:
169
WIKI.md
169
WIKI.md
@@ -17,15 +17,15 @@
|
|||||||
9. [Snapshots](#-snapshots)
|
9. [Snapshots](#-snapshots)
|
||||||
10. [IPAM](#-ipam)
|
10. [IPAM](#-ipam)
|
||||||
11. [Remote Command Execution](#-remote-command-execution)
|
11. [Remote Command Execution](#-remote-command-execution)
|
||||||
11. [Monitoring](#-monitoring)
|
12. [Monitoring](#-monitoring)
|
||||||
12. [Security (RBAC v2)](#-security)
|
13. [Security (RBAC v2)](#-security)
|
||||||
13. [Localization](#-localization)
|
14. [Localization](#-localization)
|
||||||
14. [Settings](#-settings)
|
15. [Settings](#-settings)
|
||||||
15. [API Reference](#-api-reference)
|
16. [API Reference](#-api-reference)
|
||||||
16. [Deployment Guide](#-deployment-guide)
|
17. [Deployment Guide](#-deployment-guide)
|
||||||
17. [Private Repo Updates](#-private-repo-updates)
|
18. [Private Repo Updates](#-private-repo-updates)
|
||||||
18. [Troubleshooting](#-troubleshooting)
|
19. [Troubleshooting](#-troubleshooting)
|
||||||
19. [FAQ](#-faq)
|
20. [FAQ](#-faq)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -97,16 +97,13 @@ docker compose up -d
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Database
|
# Database
|
||||||
DATABASE_URL=postgresql://postgres:password@db:5432/serverpanel
|
|
||||||
POSTGRES_PASSWORD=your_secure_password
|
POSTGRES_PASSWORD=your_secure_password
|
||||||
|
|
||||||
# Secret key (generate unique!)
|
# Secret key (generate unique!)
|
||||||
SECRET_KEY=your-very-long-secret-key-change-me
|
SECRET_KEY=your-very-long-secret-key-change-me
|
||||||
|
|
||||||
# Proxmox (optional, can be added via UI)
|
# Timezone
|
||||||
PROXMOX_HOST=192.168.1.100
|
TZ=Your/Timezone
|
||||||
PROXMOX_USER=root@pam
|
|
||||||
PROXMOX_PASSWORD=your_password
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Email and Telegram Notifications
|
#### Email and Telegram Notifications
|
||||||
@@ -151,63 +148,61 @@ This section summarizes key API endpoints. All require JWT authentication.
|
|||||||
- `POST /api/servers/{id}/vm/{vmid}/restart?node={node}` — restart VM
|
- `POST /api/servers/{id}/vm/{vmid}/restart?node={node}` — restart VM
|
||||||
- `GET /api/servers/{id}/vm/{vmid}/status?node={node}` — VM status
|
- `GET /api/servers/{id}/vm/{vmid}/status?node={node}` — VM status
|
||||||
|
|
||||||
---
|
### Containers
|
||||||
|
|
||||||
## 📦 Deployment Guide
|
- `GET /api/servers/{id}/resources` — list containers
|
||||||
|
- `POST /api/servers/{id}/container/{vmid}/start?node={node}` — start container
|
||||||
|
- `POST /api/servers/{id}/container/{vmid}/stop?node={node}` — stop container
|
||||||
|
- `POST /api/servers/{id}/container/{vmid}/restart?node={node}` — restart container
|
||||||
|
- `GET /api/servers/{id}/container/{vmid}/status?node={node}` — container status
|
||||||
|
|
||||||
Supported modes:
|
### OS Templates
|
||||||
- Standalone (dev): direct port 8000
|
|
||||||
- Production with NGINX (HTTP)
|
|
||||||
- Production with NGINX + SSL (HTTPS via Let's Encrypt)
|
|
||||||
|
|
||||||
Quick start:
|
- `GET /templates/api/groups` — list template groups
|
||||||
```bash
|
- `POST /templates/api/groups` — create template group
|
||||||
chmod +x deploy.sh
|
- `GET /templates/api/templates` — list templates
|
||||||
sudo ./deploy.sh
|
- `POST /templates/api/templates` — create template
|
||||||
```
|
- `DELETE /templates/api/templates/{id}` — delete template
|
||||||
|
|
||||||
Manual (standalone):
|
### IPAM
|
||||||
```bash
|
|
||||||
cp .env.example .env && cp backend/.env.example backend/.env
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Manual (NGINX + SSL):
|
- `GET /ipam/api/networks` — list networks
|
||||||
```bash
|
- `POST /ipam/api/networks` — create network
|
||||||
docker compose -f compose.yml -f compose.prod.yml up -d nginx
|
- `DELETE /ipam/api/networks/{id}` — delete network
|
||||||
docker compose -f compose.yml -f compose.prod.yml run --rm certbot certonly \
|
- `GET /ipam/api/allocations` — list IP allocations
|
||||||
--webroot --webroot-path=/var/www/certbot \
|
- `POST /ipam/api/allocations` — create IP allocation
|
||||||
--email admin@example.com --agree-tos --no-eff-email \
|
- `DELETE /ipam/api/allocations/{id}` — release IP
|
||||||
-d your-domain.com
|
|
||||||
docker compose -f compose.yml -f compose.prod.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Requirements:
|
### Notifications
|
||||||
- Docker 24+, Compose 2.20+
|
|
||||||
- Domain + open ports 80/443 for SSL
|
|
||||||
|
|
||||||
---
|
- `GET /api/notifications` — list notifications
|
||||||
|
- `PATCH /api/notifications/{id}/read` — mark as read
|
||||||
|
- `DELETE /api/notifications/{id}` — delete notification
|
||||||
|
|
||||||
## 🔒 Private Repo Updates
|
### Users & Roles
|
||||||
|
|
||||||
If using a private GitHub repo, update checks may fail.
|
- `GET /api/users` — list users (admin)
|
||||||
|
- `POST /api/users` — create user (admin)
|
||||||
|
- `PUT /api/users/{id}` — update user (admin)
|
||||||
|
- `DELETE /api/users/{id}` — delete user (admin)
|
||||||
|
- `GET /api/roles` — list roles (admin)
|
||||||
|
- `POST /api/roles` — create role (admin)
|
||||||
|
- `PUT /api/roles/{id}` — update role (admin)
|
||||||
|
- `DELETE /api/roles/{id}` — delete role (admin)
|
||||||
|
|
||||||
Options:
|
### Settings
|
||||||
- Disable update checks:
|
|
||||||
```bash
|
|
||||||
DISABLE_UPDATE_CHECK=true
|
|
||||||
```
|
|
||||||
- Or set GitHub token:
|
|
||||||
```bash
|
|
||||||
DISABLE_UPDATE_CHECK=false
|
|
||||||
GITHUB_TOKEN=your_token
|
|
||||||
```
|
|
||||||
Generate token: https://github.com/settings/tokens (scope `repo`).
|
|
||||||
|
|
||||||
After changes:
|
- `GET /settings/api/panel` — get panel settings
|
||||||
```bash
|
- `PUT /settings/api/panel` — update panel settings
|
||||||
docker compose restart app
|
- `GET /settings/api/security` — get security settings
|
||||||
```
|
- `PUT /settings/api/security` — update security settings
|
||||||
|
- `GET /settings/api/notifications` — get notification settings
|
||||||
|
- `PUT /settings/api/notifications` — update notification settings
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
- `GET /logs/api/logs` — get audit logs
|
||||||
|
- `GET /logs/api/stats` — get log statistics
|
||||||
|
|
||||||
## 🎯 Main Features
|
## 🎯 Main Features
|
||||||
|
|
||||||
@@ -233,6 +228,54 @@ docker compose restart app
|
|||||||
- VNC console in browser
|
- VNC console in browser
|
||||||
- Remote command execution
|
- Remote command execution
|
||||||
|
|
||||||
|
### Bulk Operations
|
||||||
|
|
||||||
|
- Mass start/stop/restart/delete VMs and containers
|
||||||
|
- Task queue system with progress tracking
|
||||||
|
- Background processing
|
||||||
|
|
||||||
|
### OS Templates
|
||||||
|
|
||||||
|
- Quick VM deployment from templates
|
||||||
|
- Cross-node template deployment for clusters
|
||||||
|
- Template groups and icons
|
||||||
|
- VM reinstall from template
|
||||||
|
|
||||||
|
### Snapshots
|
||||||
|
|
||||||
|
- Create, delete, rollback VM/LXC snapshots
|
||||||
|
- Snapshot operation queue system
|
||||||
|
- Snapshot archival on VM deletion
|
||||||
|
|
||||||
|
### IPAM (IP Address Management)
|
||||||
|
|
||||||
|
- Network and subnet management
|
||||||
|
- Automatic IP allocation
|
||||||
|
- IP reservation and release
|
||||||
|
- Allocation history
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
|
||||||
|
- Real-time alerts about VM events
|
||||||
|
- Email notifications via SMTP
|
||||||
|
- Telegram notifications via Bot API
|
||||||
|
- In-App notifications with bell icon
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- RBAC v2 with atomic permissions (`resource:action`)
|
||||||
|
- VPS-style user isolation (users see only their instances)
|
||||||
|
- IP blocking (automatic and manual)
|
||||||
|
- Session management
|
||||||
|
- Login protection (lockout, rate limiting)
|
||||||
|
- Audit logs
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
- Real-time CPU, RAM, Disk, Network metrics
|
||||||
|
- Historical graphs (hour, day, week, month)
|
||||||
|
- Resource alert thresholds
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔔 Notification System
|
## 🔔 Notification System
|
||||||
@@ -1000,10 +1043,6 @@ curl -H "Authorization: Bearer eyJ..." \
|
|||||||
| GET | `/logs/api/logs` | Get audit logs |
|
| GET | `/logs/api/logs` | Get audit logs |
|
||||||
| GET | `/logs/api/stats` | Get log statistics |
|
| GET | `/logs/api/stats` | Get log statistics |
|
||||||
|
|
||||||
### API Examples
|
|
||||||
|
|
||||||
For detailed API usage examples, see [API_EXAMPLES.md](API_EXAMPLES.md)
|
|
||||||
|
|
||||||
### Swagger Documentation
|
### Swagger Documentation
|
||||||
|
|
||||||
Available at: `http://localhost:8000/docs`
|
Available at: `http://localhost:8000/docs`
|
||||||
@@ -1170,4 +1209,4 @@ A: Not yet, but planned for future versions.
|
|||||||
---
|
---
|
||||||
|
|
||||||
*Last updated: December 2025*
|
*Last updated: December 2025*
|
||||||
*Version: 1.9.0*
|
*Version: 1.0*
|
||||||
|
|||||||
@@ -3607,6 +3607,16 @@ class I18nService:
|
|||||||
"en": "This is a test notification from PVEmanager. If you see this, notifications are working correctly!"
|
"en": "This is a test notification from PVEmanager. If you see this, notifications are working correctly!"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# Update notifications
|
||||||
|
"notify_update_available_title": {
|
||||||
|
"ru": "🆕 Доступна новая версия {new_version}",
|
||||||
|
"en": "🆕 New version {new_version} available"
|
||||||
|
},
|
||||||
|
"notify_update_available_message": {
|
||||||
|
"ru": "Доступна новая версия панели {new_version}. Текущая версия: {current_version}. Перейдите на страницу обновления для установки.",
|
||||||
|
"en": "New panel version {new_version} is available. Current version: {current_version}. Go to the update page to install."
|
||||||
|
},
|
||||||
|
|
||||||
# ==================== Users & RBAC ====================
|
# ==================== Users & RBAC ====================
|
||||||
"users_management": {
|
"users_management": {
|
||||||
"ru": "Управление пользователями",
|
"ru": "Управление пользователями",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class NotificationPreference(Base):
|
|||||||
telegram_chat_id = Column(String(100))
|
telegram_chat_id = Column(String(100))
|
||||||
webhook_url = Column(String(500))
|
webhook_url = Column(String(500))
|
||||||
notification_levels = Column(JSON, default=["critical", "warning", "info", "success"])
|
notification_levels = Column(JSON, default=["critical", "warning", "info", "success"])
|
||||||
notification_types = Column(JSON, default=["vm_status", "resource_alert", "system"])
|
notification_types = Column(JSON, default=["vm_status", "resource_alert", "system", "update"])
|
||||||
quiet_hours_start = Column(String(5)) # HH:MM format
|
quiet_hours_start = Column(String(5)) # HH:MM format
|
||||||
quiet_hours_end = Column(String(5)) # HH:MM format
|
quiet_hours_end = Column(String(5)) # HH:MM format
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|||||||
@@ -406,6 +406,43 @@ class NotificationService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def notify_update_available(
|
||||||
|
db: Session,
|
||||||
|
user_id: int,
|
||||||
|
current_version: str,
|
||||||
|
new_version: str,
|
||||||
|
changelog: Optional[str] = None
|
||||||
|
) -> Optional[Notification]:
|
||||||
|
"""Create notification about new panel version"""
|
||||||
|
lang = get_panel_language(db)
|
||||||
|
|
||||||
|
title = t("notify_update_available_title", lang, new_version=new_version)
|
||||||
|
message = t("notify_update_available_message", lang,
|
||||||
|
new_version=new_version,
|
||||||
|
current_version=current_version)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"current_version": current_version,
|
||||||
|
"new_version": new_version,
|
||||||
|
"changelog": changelog
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotificationService.create_notification(
|
||||||
|
db,
|
||||||
|
NotificationCreate(
|
||||||
|
user_id=user_id,
|
||||||
|
type="update",
|
||||||
|
level="info",
|
||||||
|
title=title,
|
||||||
|
message=message,
|
||||||
|
data=data,
|
||||||
|
link="/settings#updates",
|
||||||
|
source="system",
|
||||||
|
source_id=f"update_{new_version}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Async methods for multi-channel notifications
|
# Async methods for multi-channel notifications
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ def utcnow() -> datetime:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from backend.app.db import SessionLocal
|
from backend.app.db import SessionLocal
|
||||||
from backend.app.models import User, ProxmoxServer, PanelSettings, VMInstance, IPAMAllocation
|
from backend.app.models import User, ProxmoxServer, PanelSettings, VMInstance, IPAMAllocation, Notification
|
||||||
from backend.app.services.notification_service import NotificationService
|
from backend.app.services.notification_service import NotificationService
|
||||||
from backend.app.proxmox_client import ProxmoxClient
|
from backend.app.proxmox_client import ProxmoxClient
|
||||||
from backend.app.schemas import NotificationCreate
|
from backend.app.schemas import NotificationCreate
|
||||||
from backend.app.i18n import t
|
from backend.app.i18n import t
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from app.db import SessionLocal
|
from app.db import SessionLocal
|
||||||
from app.models import User, ProxmoxServer, PanelSettings, VMInstance, IPAMAllocation
|
from app.models import User, ProxmoxServer, PanelSettings, VMInstance, IPAMAllocation, Notification
|
||||||
from app.services.notification_service import NotificationService
|
from app.services.notification_service import NotificationService
|
||||||
from app.proxmox_client import ProxmoxClient
|
from app.proxmox_client import ProxmoxClient
|
||||||
from app.schemas import NotificationCreate
|
from app.schemas import NotificationCreate
|
||||||
@@ -815,6 +815,82 @@ class MonitoringWorker:
|
|||||||
cooldown_seconds = cooldown_minutes * 60
|
cooldown_seconds = cooldown_minutes * 60
|
||||||
return (datetime.now().timestamp() - last_alert) > cooldown_seconds
|
return (datetime.now().timestamp() - last_alert) > cooldown_seconds
|
||||||
|
|
||||||
|
def check_for_panel_updates(self):
|
||||||
|
"""
|
||||||
|
Check for panel updates and notify admin users if a new version is available.
|
||||||
|
Runs periodically (every 6 hours) via scheduler.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from app.services.update_service import check_for_updates
|
||||||
|
except ImportError:
|
||||||
|
from backend.app.services.update_service import check_for_updates
|
||||||
|
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
logger.info("[UPDATE CHECK] Checking for panel updates...")
|
||||||
|
|
||||||
|
# Run async check_for_updates
|
||||||
|
result = run_async(check_for_updates())
|
||||||
|
|
||||||
|
if result.get("error") or result.get("disabled"):
|
||||||
|
logger.debug(f"[UPDATE CHECK] Skipped: {result.get('error', 'disabled')}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not result.get("update_available"):
|
||||||
|
logger.debug(f"[UPDATE CHECK] No updates available. Current: {result.get('current_version')}")
|
||||||
|
return
|
||||||
|
|
||||||
|
current_version = result.get("current_version", "unknown")
|
||||||
|
new_version = result.get("latest_version", "unknown")
|
||||||
|
changelog = result.get("changelog")
|
||||||
|
|
||||||
|
logger.info(f"[UPDATE CHECK] New version available: {new_version} (current: {current_version})")
|
||||||
|
|
||||||
|
# Get all admin users to notify
|
||||||
|
users = db.query(User).filter(
|
||||||
|
User.is_active == True,
|
||||||
|
User.is_admin == True
|
||||||
|
).all()
|
||||||
|
|
||||||
|
if not users:
|
||||||
|
logger.debug("[UPDATE CHECK] No admin users to notify")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if we already notified about this version
|
||||||
|
for user in users:
|
||||||
|
# Check if notification already exists for this user and version
|
||||||
|
existing = db.query(Notification).filter(
|
||||||
|
Notification.user_id == user.id,
|
||||||
|
Notification.source_id == f"update_{new_version}",
|
||||||
|
Notification.type == "update"
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
logger.debug(f"[UPDATE CHECK] User {user.id} already notified about version {new_version}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create notification for this user
|
||||||
|
try:
|
||||||
|
NotificationService.notify_update_available(
|
||||||
|
db=db,
|
||||||
|
user_id=user.id,
|
||||||
|
current_version=current_version,
|
||||||
|
new_version=new_version,
|
||||||
|
changelog=changelog
|
||||||
|
)
|
||||||
|
logger.info(f"[UPDATE CHECK] Notified user {user.username} about update to {new_version}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[UPDATE CHECK] Failed to notify user {user.id}: {e}")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"[UPDATE CHECK] Completed. Notified {len(users)} admin users about version {new_version}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[UPDATE CHECK] Error checking for updates: {e}", exc_info=True)
|
||||||
|
db.rollback()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
# Global worker instance
|
# Global worker instance
|
||||||
monitoring_worker = MonitoringWorker()
|
monitoring_worker = MonitoringWorker()
|
||||||
@@ -855,6 +931,15 @@ def start_monitoring_worker():
|
|||||||
replace_existing=True
|
replace_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Update check - every 6 hours
|
||||||
|
scheduler.add_job(
|
||||||
|
monitoring_worker.check_for_panel_updates,
|
||||||
|
trigger=IntervalTrigger(hours=6),
|
||||||
|
id='update_check',
|
||||||
|
name='Check for panel updates',
|
||||||
|
replace_existing=True
|
||||||
|
)
|
||||||
|
|
||||||
# VM cache sync - every 30 seconds
|
# VM cache sync - every 30 seconds
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
monitoring_worker.sync_vm_cache,
|
monitoring_worker.sync_vm_cache,
|
||||||
@@ -894,6 +979,19 @@ def start_monitoring_worker():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Initial VM cache sync failed: {e}")
|
logger.warning(f"Initial VM cache sync failed: {e}")
|
||||||
|
|
||||||
|
# Run initial update check on startup (delayed by 60 seconds to let the app fully start)
|
||||||
|
def delayed_update_check():
|
||||||
|
import time
|
||||||
|
time.sleep(60)
|
||||||
|
try:
|
||||||
|
logger.info("Running initial update check...")
|
||||||
|
monitoring_worker.check_for_panel_updates()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Initial update check failed: {e}")
|
||||||
|
|
||||||
|
import threading
|
||||||
|
threading.Thread(target=delayed_update_check, daemon=True).start()
|
||||||
|
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
logger.info("Background monitoring worker started")
|
logger.info("Background monitoring worker started")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user