From 890f3a0411fdbe258df92a562b8c782fab7bc9a3 Mon Sep 17 00:00:00 2001
From: UNITRONIX <36471318+UNITRONIX@users.noreply.github.com>
Date: Sun, 1 Feb 2026 01:14:16 +0100
Subject: [PATCH] Clean up v14 assets, add v15 deploy script and cache busting
Removed obsolete v14 static and template files, and deleted legacy scripts and performance config. Added scripts/deploy_v15.sh for clean v15 deployment. Updated app_v14.py to support cache busting via version injection and cache headers. Updated documentation and troubleshooting to reflect new manual key permission steps and streamlined upgrade process. Bumped version to v1.5.1.
---
BUGFIX_CHANGELOG.md | 244 --------
README.md | 58 +-
VERSION | 26 +-
docs/KEY_TROUBLESHOOTING.md | 33 +-
scripts/deploy_v15.sh | 120 ++++
web/app_v14.py | 37 ++
web/static/.gitkeep | 0
web/static/client_generator.js | 28 +-
web/static/performance-config.css | 168 -----
web/static/script.js | 519 ----------------
web/static/script_v14.js | 924 ----------------------------
web/static/sidebar.css | 412 -------------
web/static/sidebar.js | 209 -------
web/templates/client_generator.html | 219 +++++--
web/templates/index_v14.html | 536 ----------------
web/templates/index_v15.html | 10 +-
web/templates/login.html | 5 +-
17 files changed, 405 insertions(+), 3143 deletions(-)
delete mode 100644 BUGFIX_CHANGELOG.md
create mode 100644 scripts/deploy_v15.sh
delete mode 100644 web/static/.gitkeep
delete mode 100644 web/static/performance-config.css
delete mode 100644 web/static/script.js
delete mode 100644 web/static/script_v14.js
delete mode 100644 web/static/sidebar.css
delete mode 100644 web/static/sidebar.js
delete mode 100644 web/templates/index_v14.html
diff --git a/BUGFIX_CHANGELOG.md b/BUGFIX_CHANGELOG.md
deleted file mode 100644
index b2bd769f..00000000
--- a/BUGFIX_CHANGELOG.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# 📝 Changelog - Naprawa Problemów Użytkowników
-
-## [31 Stycznia 2026] - Naprawa Krytycznych Błędów
-
-### 🐛 Naprawione Problemy
-
-#### Problem 1: Docker - "sh: executable file not found in $PATH"
-**Zgłaszający:** Użytkownik GitHub
-**Symptomy:**
-```
-Error response from daemon: failed to create task for container:
-failed to create shim task: OCI runtime create failed:
-runc create failed: unable to start container process:
-error during container init: exec: "sh": executable file not found in $PATH
-```
-
-**Przyczyna:**
-- Obraz bazowy `python:3.11-slim` w niektórych przypadkach nie zawiera bash
-- Skrypt `docker-entrypoint.sh` wymaga bash ze względu na zaawansowane funkcje
-
-**Rozwiązanie:**
-- ✅ Dodano instalację `bash` do `Dockerfile.console`
-- ✅ Dodano dokumentację wyjaśniającą wymagania shell'a
-- ✅ Zmieniono komentarze w `docker-entrypoint.sh`
-
-**Zmodyfikowane pliki:**
-- `Dockerfile.console` - dodano bash do apt-get install
-- `docker-entrypoint.sh` - dodano komentarz wyjaśniający
-
----
-
-#### Problem 2: PowerShell - "Write-Info is not recognized"
-**Zgłaszający:** Użytkownik Windows 11
-**Symptomy:**
-```powershell
-The term 'Write-Info' is not recognized as the name of a cmdlet,
-function, script file, or operable program.
-```
-
-**Przyczyna:**
-- Konflikt nazw funkcji z wbudowanymi cmdletami PowerShell (`Write-Error`, `Write-Warning`)
-- Niektóre wersje PowerShell mogą mieć problemy z niestandardowymi funkcjami
-- Brak wymogu minimalnej wersji PowerShell
-
-**Rozwiązanie:**
-- ✅ Zmieniono nazwy funkcji pomocniczych:
- - `Write-Error` → `Write-ErrorMsg`
- - `Write-Warning` → `Write-WarningMsg`
- - `Write-Info` → `Write-InfoMsg`
-- ✅ Dodano `#Requires -Version 5.1`
-- ✅ Dodano `Set-StrictMode -Version Latest`
-- ✅ Dodano regiony dla lepszej organizacji kodu
-- ✅ Zamieniono wszystkie 58 wywołań funkcji na nowe nazwy
-
-**Zmodyfikowane pliki:**
-- `install-improved.ps1` - pełna refaktoryzacja funkcji helper
-
----
-
-### 📄 Nowe Pliki Dokumentacji
-
-#### 1. `TROUBLESHOOTING.md`
-Kompletny przewodnik rozwiązywania problemów zawierający:
-- Szczegółowy opis obu problemów
-- Dokładne przyczyny i rozwiązania
-- Instrukcje testowania
-- Diagnostykę dla zaawansowanych użytkowników
-- Alternatywne rozwiązania
-
-#### 2. `QUICK_FIX.md`
-Szybki przewodnik dla zgłaszających problemy:
-- Krok po kroku instrukcje naprawy
-- Komendy do skopiowania i wklejenia
-- Checklist weryfikacji
-- Informacje diagnostyczne do zgłoszenia jeśli problemy persist
-
-#### 3. `OPTIMIZATION_SUMMARY.md`
-Podsumowanie optymalizacji GPU (wcześniejsza praca):
-- Lista zoptymalizowanych plików
-- Metryki wydajności
-- Instrukcje dla użytkowników
-
-#### 4. `docs/GPU_OPTIMIZATION.md`
-Szczegółowa dokumentacja optymalizacji wydajności panelu web
-
-#### 5. `docs/GPU_FIX_QUICKSTART.md`
-Szybki start dla problemów z wydajnością GPU
-
-#### 6. `web/static/performance-config.css`
-Plik konfiguracyjny z 4 profilami wydajności
-
----
-
-### 🔄 Zaktualizowane Pliki
-
-#### `README.md`
-- ✅ Dodano sekcję "Recent Fixes" na górze Troubleshooting
-- ✅ Dodano linki do nowych przewodników
-- ✅ Podkreślono naprawione problemy
-
-#### `Dockerfile.console`
-```diff
-+ RUN apt-get update && apt-get install -y \
-+ sqlite3 \
-+ curl \
-+ bash \
-+ && rm -rf /var/lib/apt/lists/*
-```
-
-#### `docker-entrypoint.sh`
-```diff
- #!/bin/bash
-+ # Docker Entrypoint for BetterDesk Console
-+ # This script requires bash due to array syntax and advanced features
- set -e
-```
-
-#### `install-improved.ps1`
-```diff
-+ #Requires -Version 5.1
-+ Set-StrictMode -Version Latest
-
-+ #region Helper Functions
-- function Write-Error { ... }
-- function Write-Warning { ... }
-- function Write-Info { ... }
-+ function Write-ErrorMsg { ... }
-+ function Write-WarningMsg { ... }
-+ function Write-InfoMsg { ... }
-+ #endregion
-
-# + 58 zmian wywołań funkcji w całym pliku
-```
-
----
-
-### 📊 Statystyki Zmian
-
-| Kategoria | Wartość |
-|-----------|---------|
-| Zmodyfikowane pliki | 4 |
-| Nowe pliki dokumentacji | 6 |
-| Linie kodu zmienionych | ~150 |
-| Wywołania funkcji zaktualizowanych | 58 |
-| Problemy naprawione | 2 |
-| Zgłaszający pomóc | 2+ |
-
----
-
-### ✅ Weryfikacja
-
-#### Docker:
-```bash
-# Test kompilacji
-docker-compose build --no-cache betterdesk-console
-✅ Buduje się bez błędów
-
-# Test uruchomienia
-docker-compose up -d betterdesk-console
-✅ Kontener startuje poprawnie
-
-# Test funkcjonalności
-docker-compose logs betterdesk-console | grep "Starting BetterDesk Console"
-✅ Aplikacja się uruchamia
-```
-
-#### PowerShell:
-```powershell
-# Test składni
-Get-Command .\install-improved.ps1 -Syntax
-✅ Składnia poprawna
-
-# Test wykonania
-.\install-improved.ps1 -WhatIf
-✅ Uruchamia się bez błędów
-
-# Test funkcji
-(Get-Content .\install-improved.ps1) -match "Write-ErrorMsg|Write-WarningMsg|Write-InfoMsg"
-✅ Wszystkie funkcje zaktualizowane
-```
-
----
-
-### 🎯 Dla Zgłaszających
-
-#### Użytkownik Problem 1 (Docker):
-```bash
-git pull origin main
-docker-compose down -v
-docker-compose build --no-cache
-docker-compose up -d
-docker-compose logs -f betterdesk-console
-```
-**Status:** ✅ Powinno działać
-
-#### Użytkownik Problem 2 (PowerShell):
-```powershell
-git pull origin main
-.\install-improved.ps1
-```
-**Status:** ✅ Powinno działać
-
----
-
-### 📞 Wsparcie
-
-Jeśli problemy nadal występują po aktualizacji:
-
-1. Sprawdź dokumentację:
- - [QUICK_FIX.md](QUICK_FIX.md) dla szybkich rozwiązań
- - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) dla szczegółów
-
-2. Uruchom diagnostykę:
- ```bash
- # Docker
- docker version
- docker-compose config
-
- # PowerShell
- $PSVersionTable
- Get-ExecutionPolicy -List
- ```
-
-3. Zgłoś issue na GitHub z:
- - Opisem problemu
- - Wyjściem z diagnostyki
- - Logami błędów
-
----
-
-### 🙏 Podziękowania
-
-Dziękujemy użytkownikom za zgłoszenie problemów:
-- Użytkownik zgłaszający problem Docker
-- Użytkownik zgłaszający problem PowerShell
-
-Wasze zgłoszenia pomogły ulepszyć projekt dla całej społeczności!
-
----
-
-**Data:** 31 Stycznia 2026
-**Wersja:** 1.5.0
-**Autor napraw:** UNITRONIX Team + GitHub Copilot
-**Status:** ✅ Ukończone i przetestowane
diff --git a/README.md b/README.md
index 28a7b9fc..c3811323 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@



-
+


@@ -306,27 +306,29 @@ sudo ./install-improved.sh
### 🔄 Updating Existing Installation
-If you already have BetterDesk Console installed and want to upgrade to v1.5.0:
+If you already have BetterDesk Console installed, the same `install-improved.sh` script handles updates:
```bash
cd Rustdesk-FreeConsole
-# Make the update script executable
-chmod +x update-to-v1.5.0.sh
+# Pull latest changes
+git pull origin main
-# Run as root
-sudo ./update-to-v1.5.0.sh
+# Run installer (auto-detects existing installation)
+chmod +x install-improved.sh
+sudo ./install-improved.sh
```
**Update features:**
+- ✅ Auto-detects existing BetterDesk installation
- ✅ Automatic backup before changes
- ✅ Database migration (adds `last_online`, `is_deleted` columns)
- ✅ Authentication tables creation
- ✅ API key generation and configuration
-- ✅ Preserves existing configuration
+- ✅ Preserves existing configuration and encryption keys
- ✅ Creates default admin user (if needed)
- ✅ Updates HBBS/HBBR binaries
-- ✅ Rollback capability if update fails
+- ✅ Version detection and smart upgrade path
### 🐳 Docker Installation & Update
@@ -499,14 +501,7 @@ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}"
This is the most common issue after installation. **Don't panic!**
-**Quick Fix:**
-```bash
-cd /path/to/Rustdesk-FreeConsole
-sudo bash repair-keys.sh
-# Select option 5: Restore from backup
-```
-
-**If BetterDesk broke your setup:**
+**Quick Fix - Restore from backup:**
```bash
# Find most recent backup
BACKUP=$(ls -d /opt/rustdesk-backup-* | sort | tail -1)
@@ -529,20 +524,21 @@ cat /opt/rustdesk/id_ed25519.pub
- **[KEY_TROUBLESHOOTING.md](docs/KEY_TROUBLESHOOTING.md)** - Complete key management guide
- **[UPDATE_GUIDE.md](docs/UPDATE_GUIDE.md)** - Updating existing installations
-### 🔧 Using the Repair Tool
+### 🔧 Key Permission Issues
-The `repair-keys.sh` tool can fix most key-related issues:
+If you experience key permission problems, fix them manually:
```bash
-sudo bash repair-keys.sh
-```
+# Set correct permissions for encryption keys
+sudo chmod 600 /opt/rustdesk/id_ed25519
+sudo chmod 644 /opt/rustdesk/id_ed25519.pub
-**Available options:**
-1. 📋 Show current key information
-2. 🔐 Verify and fix key permissions
-3. 📤 Export public key
-4. 🔄 Regenerate keys (⚠️ BREAKS connections)
-5. 💾 Restore keys from backup
+# Restart services
+sudo systemctl restart rustdesksignal rustdeskrelay betterdesk
+
+# Verify public key is readable
+cat /opt/rustdesk/id_ed25519.pub
+```
### 🐳 Docker Issues
@@ -683,9 +679,9 @@ conn.close()
| Symptom | Cause | Solution |
|---------|-------|----------|
-| "Key mismatch" | Keys changed during install | Restore from backup or use repair tool |
+| "Key mismatch" | Keys changed during install | Restore from backup (see Troubleshooting above) |
| Wrong key in WebConsole | Multiple `.pub` files | Remove incorrect files or upgrade to v9+ |
-| Services won't start | Permission issues | Run `sudo bash repair-keys.sh` → option 2 |
+| Services won't start | Permission issues | Fix with: `sudo chmod 600 /opt/rustdesk/id_ed25519` |
| Can't find backups | Skipped backup during install | Check `/opt/rustdesk-backup-*` directories |
| Docker detected | Running RustDesk in container | Choose "Web Console only" option |
| **No admin login (Docker)** | Missing database migration | Run `./fix-admin.sh` or see Docker Issues section |
@@ -748,9 +744,9 @@ sudo systemctl daemon-reload
sudo systemctl restart hbbs
```
-#### 4. Update Script Cannot Find Installation
+#### 4. Installer Cannot Find Installation
-**Symptoms:** `update-to-v1.5.0.sh` reports "Installation directory not found"
+**Symptoms:** `install-improved.sh` reports "Installation directory not found"
**Cause:** Non-standard installation path.
@@ -768,7 +764,7 @@ python3 migrations/v1.5.0_fix_online_status.py /path/to/db_v2.sqlite3
**Before asking for help:**
1. Check the troubleshooting guides above
-2. Try the repair tool: `sudo bash repair-keys.sh`
+2. Run the installation script again (it auto-detects and can fix issues): `sudo ./install-improved.sh`
3. Collect diagnostics:
```bash
sudo journalctl -u rustdesksignal -n 50 > ~/rustdesk_logs.txt
diff --git a/VERSION b/VERSION
index e1d3178f..8df3a296 100644
--- a/VERSION
+++ b/VERSION
@@ -1,9 +1,15 @@
-v1.5.0
+v1.5.1
-BetterDesk Console Version 1.5.0
-Authentication System & Modern UI
+BetterDesk Console Version 1.5.1
+GPU Optimization & Clean Deployment
-New in v1.5.0:
+New in v1.5.1:
+- 🚀 GPU optimization (removed backdrop-filter, heavy animations)
+- 🧹 Clean project structure (removed old files)
+- 🔒 Client Generator disabled (under development)
+- 🔄 Cache busting system for automatic updates
+
+Previous v1.5.0:
- 🔐 Authentication system with bcrypt password hashing
- 👥 Role-based access control (Admin, Operator, Viewer)
- 🎨 Sidebar navigation with 5 main sections
@@ -12,8 +18,7 @@ New in v1.5.0:
- 👤 User Management panel (admin only)
- 🛡️ CSRF protection and rate limiting
- 📊 Extended About page with open source credits
-- 🔧 New repair-keys.sh diagnostic tool
-- 📚 Complete troubleshooting documentation
+- � Complete troubleshooting documentation
- ⚠️ Visual warnings for dangerous operations
- ✅ Pre-flight validation checks
@@ -24,14 +29,11 @@ Critical Fixes:
- Fixed Docker installation conflicts
- Added automatic key backup verification
-Installation:
+Installation & Update:
sudo bash install-improved.sh
-Repair Tool:
-sudo bash repair-keys.sh
-
Documentation:
- docs/KEY_TROUBLESHOOTING.md - Complete guide
-- docs/QUICK_FIX.md - Fast solutions
-- docs/RELEASE_NOTES_v9.md - Full changelog
+- docs/UPDATE_GUIDE.md - Update instructions
+- README.md - Full documentation
diff --git a/docs/KEY_TROUBLESHOOTING.md b/docs/KEY_TROUBLESHOOTING.md
index bed54920..05c27d31 100644
--- a/docs/KEY_TROUBLESHOOTING.md
+++ b/docs/KEY_TROUBLESHOOTING.md
@@ -145,21 +145,26 @@ sudo systemctl start rustdesksignal rustdeskrelay
cat /opt/rustdesk/id_ed25519.pub
```
-### Solution 2: Using the Repair Tool
+### Solution 2: Fix Key Permissions Manually
-**Easiest method** - use the included repair tool:
+**Easiest method** - fix permissions directly:
```bash
-cd /path/to/Rustdesk-FreeConsole
-sudo bash repair-keys.sh
+# Set correct permissions for encryption keys
+sudo chmod 600 /opt/rustdesk/id_ed25519
+sudo chmod 644 /opt/rustdesk/id_ed25519.pub
+
+# Verify ownership
+sudo chown root:root /opt/rustdesk/id_ed25519*
+
+# Restart services
+sudo systemctl restart rustdesksignal rustdeskrelay betterdesk
```
-**Options available:**
-1. Show current key information
-2. Verify and fix permissions
-3. Export public key
-4. Regenerate keys (last resort)
-5. Restore from backup
+**Verify it works:**
+```bash
+cat /opt/rustdesk/id_ed25519.pub
+```
### Solution 3: Multiple .pub Files Exist
@@ -200,14 +205,6 @@ stat /opt/rustdesk/*.pub
**Fix - Last Resort** (regenerate keys):
-```bash
-# Use repair tool for safety
-sudo bash repair-keys.sh
-# Select option 4: Regenerate keys
-```
-
-**OR manually:**
-
```bash
# STOP! Make backup first!
sudo cp -r /opt/rustdesk /opt/rustdesk-backup-emergency
diff --git a/scripts/deploy_v15.sh b/scripts/deploy_v15.sh
new file mode 100644
index 00000000..e08cecca
--- /dev/null
+++ b/scripts/deploy_v15.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+# ==============================================
+# BetterDesk Console v1.5 - Complete Clean Deployment
+# Date: 2026-02-01
+# ==============================================
+
+set -e
+
+echo "========================================"
+echo " BetterDesk Console v1.5 Deployment"
+echo "========================================"
+echo ""
+
+WEB_DIR="/opt/BetterDeskConsole/web"
+BACKUP_DIR="/opt/BetterDeskConsole/backups/pre_v15_$(date +%Y%m%d_%H%M%S)"
+
+# Create backup
+echo "[1/5] Creating backup..."
+sudo mkdir -p "$BACKUP_DIR"
+sudo cp -r "$WEB_DIR" "$BACKUP_DIR/"
+echo " Backup created: $BACKUP_DIR"
+
+# Clean old files from templates
+echo ""
+echo "[2/5] Cleaning old template files..."
+sudo rm -f "$WEB_DIR/templates/base.html"
+sudo rm -f "$WEB_DIR/templates/clients.html"
+sudo rm -f "$WEB_DIR/templates/dashboard.html"
+sudo rm -f "$WEB_DIR/templates/minimal_client.html"
+sudo rm -f "$WEB_DIR/templates/minimal_client.html.backup"
+sudo rm -f "$WEB_DIR/templates/settings.html"
+sudo rm -f "$WEB_DIR/templates/updates.html"
+sudo rm -f "$WEB_DIR/templates/index_v14.html"
+echo " Old templates removed"
+
+# Clean old files from static
+echo ""
+echo "[3/5] Cleaning old static files..."
+sudo rm -f "$WEB_DIR/static/clients.css"
+sudo rm -f "$WEB_DIR/static/clients.js"
+sudo rm -f "$WEB_DIR/static/dashboard.css"
+sudo rm -f "$WEB_DIR/static/dashboard.js"
+sudo rm -f "$WEB_DIR/static/minimal_client.css"
+sudo rm -f "$WEB_DIR/static/minimal_client.css.backup"
+sudo rm -f "$WEB_DIR/static/minimal_client.js"
+sudo rm -f "$WEB_DIR/static/minimal_client.js.backup2"
+sudo rm -f "$WEB_DIR/static/settings.css"
+sudo rm -f "$WEB_DIR/static/sidebar.js"
+sudo rm -f "$WEB_DIR/static/sidebar.css"
+sudo rm -f "$WEB_DIR/static/updates.css"
+sudo rm -f "$WEB_DIR/static/updates.js"
+sudo rm -f "$WEB_DIR/static/performance-config.css"
+sudo rm -f "$WEB_DIR/static/script_v14.js"
+sudo rm -f "$WEB_DIR/static/script.js"
+echo " Old static files removed"
+
+# Clean old Python files
+echo ""
+echo "[4/5] Cleaning old Python files..."
+sudo rm -f "$WEB_DIR/app.py"
+sudo rm -f "$WEB_DIR/app.py.backup-"
+echo " Old Python files removed"
+
+# Deploy new files from /tmp/
+echo ""
+echo "[5/5] Deploying new files..."
+if [ -f /tmp/deploy_v15/app_v14.py ]; then
+ # Templates
+ sudo cp /tmp/deploy_v15/index_v15.html "$WEB_DIR/templates/"
+ sudo cp /tmp/deploy_v15/login.html "$WEB_DIR/templates/"
+ sudo cp /tmp/deploy_v15/client_generator.html "$WEB_DIR/templates/"
+
+ # Static files
+ sudo cp /tmp/deploy_v15/style.css "$WEB_DIR/static/"
+ sudo cp /tmp/deploy_v15/script_v15.js "$WEB_DIR/static/"
+ sudo cp /tmp/deploy_v15/client_generator.css "$WEB_DIR/static/"
+ sudo cp /tmp/deploy_v15/client_generator.js "$WEB_DIR/static/"
+
+ # Python app
+ sudo cp /tmp/deploy_v15/app_v14.py "$WEB_DIR/"
+ sudo cp /tmp/deploy_v15/auth.py "$WEB_DIR/"
+ sudo cp /tmp/deploy_v15/client_generator_module.py "$WEB_DIR/"
+
+ echo " New files deployed"
+else
+ echo " ERROR: Deploy files not found in /tmp/deploy_v15/"
+ echo " Please upload files first"
+ exit 1
+fi
+
+# Set permissions
+echo ""
+echo "Setting permissions..."
+# Use current user as owner (parameterized for different environments)
+DEPLOY_USER="${DEPLOY_USER:-$(whoami)}"
+sudo chown -R "$DEPLOY_USER:$DEPLOY_USER" "$WEB_DIR"
+sudo chmod -R 755 "$WEB_DIR"
+
+# Restart service
+echo ""
+echo "Restarting BetterDesk service..."
+sudo systemctl restart betterdesk
+sleep 2
+
+# Show status
+echo ""
+echo "========================================"
+echo " Deployment Complete!"
+echo "========================================"
+echo ""
+echo "Files deployed:"
+ls -la "$WEB_DIR/templates/" 2>/dev/null | grep -E "\.html$" | awk '{print " • " $NF}'
+echo ""
+ls -la "$WEB_DIR/static/" 2>/dev/null | grep -E "\.(css|js)$" | awk '{print " • " $NF}'
+echo ""
+echo "Service status:"
+sudo systemctl status betterdesk --no-pager | head -5
+echo ""
+echo "Backup location: $BACKUP_DIR"
+echo ""
diff --git a/web/app_v14.py b/web/app_v14.py
index f4563cd3..d21f4d06 100644
--- a/web/app_v14.py
+++ b/web/app_v14.py
@@ -26,6 +26,43 @@ app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', os.urandom(32))
app.config['WTF_CSRF_CHECK_DEFAULT'] = False # Manual CSRF for API
+# Load version for cache busting
+VERSION_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'VERSION')
+APP_VERSION = 'v1.5.0' # Default
+try:
+ if os.path.exists(VERSION_FILE):
+ with open(VERSION_FILE, 'r') as f:
+ version_line = f.readline().strip()
+ if version_line:
+ APP_VERSION = version_line
+except:
+ pass
+
+# Context processor to inject version into all templates
+@app.context_processor
+def inject_version():
+ return {'app_version': APP_VERSION}
+
+# Cache control for static files
+@app.after_request
+def add_cache_headers(response):
+ """Add cache control headers to responses."""
+ if request.path.startswith('/static/'):
+ # Static files: cache for 1 year if versioned, otherwise 5 minutes
+ if 'v=' in request.query_string.decode():
+ response.cache_control.max_age = 31536000 # 1 year
+ response.cache_control.public = True
+ else:
+ response.cache_control.max_age = 300 # 5 minutes
+ elif request.path == '/' or request.path.endswith('.html'):
+ # HTML pages: no cache (always get fresh)
+ response.cache_control.no_cache = True
+ response.cache_control.no_store = True
+ response.cache_control.must_revalidate = True
+ response.headers['Pragma'] = 'no-cache'
+ response.headers['Expires'] = '0'
+ return response
+
# Initialize CSRF protection
csrf = CSRFProtect()
csrf.init_app(app)
diff --git a/web/static/.gitkeep b/web/static/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/web/static/client_generator.js b/web/static/client_generator.js
index fec0b390..03ada267 100644
--- a/web/static/client_generator.js
+++ b/web/static/client_generator.js
@@ -13,11 +13,25 @@ document.addEventListener('DOMContentLoaded', function() {
function checkAuthentication() {
const token = localStorage.getItem('authToken');
+ const username = localStorage.getItem('username');
+ const role = localStorage.getItem('role');
+
if (!token) {
window.location.href = '/login';
return;
}
+ // Setup user info in sidebar
+ document.getElementById('sidebarUsername').textContent = username || 'User';
+ document.getElementById('sidebarRole').textContent = role || 'viewer';
+
+ // Show admin-only sections
+ if (role === 'admin') {
+ document.querySelectorAll('.admin-only').forEach(el => {
+ el.style.display = '';
+ });
+ }
+
// Verify token
fetch('/api/auth/verify', {
headers: {
@@ -27,20 +41,10 @@ function checkAuthentication() {
.then(response => {
if (!response.ok) {
localStorage.removeItem('authToken');
+ localStorage.removeItem('username');
+ localStorage.removeItem('role');
window.location.href = '/login';
}
- return response.json();
- })
- .then(data => {
- if (data.user) {
- document.getElementById('sidebarUsername').textContent = data.user.username || 'User';
- const roleNames = {
- 'admin': 'Administrator',
- 'operator': 'Operator',
- 'viewer': 'Viewer'
- };
- document.getElementById('sidebarUserRole').textContent = roleNames[data.user.role] || data.user.role;
- }
})
.catch(() => {
window.location.href = '/login';
diff --git a/web/static/performance-config.css b/web/static/performance-config.css
deleted file mode 100644
index 6715ddb5..00000000
--- a/web/static/performance-config.css
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * Performance Configuration for BetterDesk Console
- *
- * Include this file AFTER style.css in your HTML to apply additional optimizations
- * Include this file AFTER style.css in your HTML to apply additional optimizations
- *
- *
- */
-
-/* ============================================
- PROFILE 1: MAXIMUM PERFORMANCE (WEAK GPU)
- PROFILE 1: MAXIMUM PERFORMANCE (WEAK GPU)
- ============================================ */
-
-/* Uncomment the section below for maximum performance */
-/* Uncomment section below for maximum performance */
-
-/*
-* {
- animation: none !important;
- transition: none !important;
-}
-
-.bg-gradient {
- display: none !important;
-}
-
-.glass-effect,
-.stat-card,
-.btn,
-.modal {
- box-shadow: none !important;
-}
-
-.btn:hover,
-.stat-card:hover,
-.action-btn:hover {
- transform: none !important;
-}
-*/
-
-/* ============================================
- PROFILE 2: BALANCED (MEDIUM GPU)
- PROFILE 2: BALANCED (MEDIUM GPU)
- ============================================ */
-
-/* Active by default - minimal animations */
-/* Active by default - minimal animations */
-
-/* Shorter transition times */
-/* Shorter transition times */
-.btn,
-.stat-card,
-.modal,
-.action-btn {
- transition-duration: 0.15s !important;
-}
-
-/* Zmniejszone cienie */
-/* Reduced shadows */
-.glass-effect,
-.stat-card {
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
-}
-
-/* Disable gradient backgrounds in buttons */
-/* Disable gradient backgrounds in buttons */
-.btn-primary {
- background: var(--primary-color) !important;
-}
-
-.btn-danger {
- background: var(--danger-color) !important;
-}
-
-/* ============================================
- PROFIL 3: PREMIUM (MOCNE GPU)
- PROFILE 3: PREMIUM (STRONG GPU)
- ============================================ */
-
-/* Uncomment below for full visual effects */
-/* Uncomment below for full visual effects */
-
-/*
-.glass-effect {
- backdrop-filter: blur(8px) !important;
- -webkit-backdrop-filter: blur(8px) !important;
-}
-
-.sidebar {
- background: rgba(255, 255, 255, 0.05) !important;
- backdrop-filter: blur(15px) !important;
- -webkit-backdrop-filter: blur(15px) !important;
-}
-
-.modal {
- background: rgba(0, 0, 0, 0.7) !important;
- backdrop-filter: blur(5px) !important;
- -webkit-backdrop-filter: blur(5px) !important;
-}
-
-.bg-gradient {
- width: 150% !important;
- height: 150% !important;
- animation: gradientShiftSmooth 15s ease-in-out infinite !important;
-}
-
-@keyframes gradientShiftSmooth {
- 0%, 100% {
- transform: translate(-16%, -16%) scale(1.1);
- opacity: 1;
- }
- 50% {
- transform: translate(-20%, -20%) scale(1.15);
- opacity: 0.98;
- }
-}
-
-.btn-primary {
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
-}
-
-.btn-danger {
- background: linear-gradient(135deg, var(--danger-color), #ff6b6b) !important;
-}
-*/
-
-/* ============================================
- PROFILE 4: ULTRA ECONOMICAL (VERY WEAK GPU)
- PROFILE 4: ULTRA SAVING (VERY WEAK GPU)
- ============================================ */
-
-/* Uncomment below only in case of extreme problems */
-/* Uncomment below only for extreme performance issues */
-
-/*
-* {
- animation: none !important;
- transition: none !important;
- transform: none !important;
- box-shadow: none !important;
-}
-
-.bg-gradient,
-.stat-icon,
-.btn::before,
-.btn::after {
- display: none !important;
-}
-
-.glass-effect,
-.sidebar,
-.modal {
- background: rgba(30, 30, 30, 0.98) !important;
-}
-
-.btn:hover,
-.stat-card:hover {
- opacity: 0.9 !important;
-}
-*/
-
-/* ============================================
- CUSTOM TWEAKS
- ============================================ */
-
-/* Add your own customizations below */
-/* Add your custom tweaks below */
diff --git a/web/static/script.js b/web/static/script.js
deleted file mode 100644
index 28593d64..00000000
--- a/web/static/script.js
+++ /dev/null
@@ -1,519 +0,0 @@
-// Global variables
-let allDevices = [];
-let currentDeviceId = null;
-
-// Initialize on page load
-document.addEventListener('DOMContentLoaded', function() {
- loadDevices();
- loadStats();
-
- // Auto-refresh every 2 seconds
- setInterval(() => {
- loadDevices();
- loadStats();
- }, 2000);
-});
-
-// Load devices from API
-async function loadDevices() {
- try {
- const response = await fetch('/api/devices');
- const data = await response.json();
-
- if (data.success) {
- allDevices = data.devices;
- renderDevices(allDevices);
- updateNavStats(allDevices);
- } else {
- showToast('Error loading devices: ' + data.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to load devices', 'error');
- }
-}
-
-// Load statistics
-async function loadStats() {
- try {
- const response = await fetch('/api/stats');
- const data = await response.json();
-
- if (data.success) {
- document.getElementById('statTotal').textContent = data.stats.total;
- document.getElementById('statActive').textContent = data.stats.active;
- document.getElementById('statInactive').textContent = data.stats.inactive;
- document.getElementById('statBanned').textContent = data.stats.banned || 0;
- document.getElementById('statNotes').textContent = data.stats.with_notes;
- }
- } catch (error) {
- console.error('Error loading stats:', error);
- }
-}
-
-// Update navigation stats
-function updateNavStats(devices) {
- const total = devices.length;
- const active = devices.filter(d => d.online).length;
-
- document.querySelector('#totalDevices span').textContent = total;
- document.querySelector('#activeDevices span').textContent = active;
-}
-
-// Render devices table
-function renderDevices(devices) {
- const tbody = document.getElementById('devicesTableBody');
-
- if (devices.length === 0) {
- tbody.innerHTML = `
-
- |
-
- No devices found
- |
-
- `;
- return;
- }
-
- tbody.innerHTML = devices.map(device => {
- const isBanned = device.is_banned === true || device.is_banned === 1;
- const rowClass = isBanned ? 'style="opacity: 0.6; background: rgba(255, 0, 0, 0.05);"' : '';
-
- return `
-
-
- ${escapeHtml(device.id)}
- ${isBanned ? ' BANNED' : ''}
- |
- ${escapeHtml(device.note) || 'No note'} |
-
-
-
- ${device.online ? 'Online' : 'Offline'}
-
- |
- ${formatDate(device.created_at)} |
-
-
-
-
- ${isBanned ?
- `` :
- ``
- }
-
- |
-
- `}).join('');
-}
-
-// Filter devices by search
-function filterDevices() {
- const searchTerm = document.getElementById('searchInput').value.toLowerCase();
-
- if (!searchTerm) {
- renderDevices(allDevices);
- return;
- }
-
- const filtered = allDevices.filter(device =>
- device.id.toLowerCase().includes(searchTerm) ||
- (device.note && device.note.toLowerCase().includes(searchTerm))
- );
-
- renderDevices(filtered);
-}
-
-// Connect to device via rustdesk:// protocol
-function connectDevice(deviceId) {
- window.location.href = `rustdesk://${deviceId}`;
- showToast(`Connecting to ${deviceId}...`);
-}
-
-// Show device details modal
-function showDetails(deviceId) {
- const device = allDevices.find(d => d.id === deviceId);
- if (!device) return;
-
- const isBanned = device.is_banned === true || device.is_banned === 1;
-
- const detailsContent = document.getElementById('detailsContent');
- detailsContent.innerHTML = `
-
-
ID:
-
${escapeHtml(device.id)}
-
-
-
GUID:
-
${escapeHtml(device.guid) || 'N/A'}
-
-
-
UUID:
-
${escapeHtml(device.uuid) || 'N/A'}
-
-
-
Public Key:
-
${escapeHtml(device.pk) || 'N/A'}
-
-
-
User:
-
${escapeHtml(device.user) || 'N/A'}
-
-
-
Status:
-
-
-
- ${device.online ? 'Online' : 'Offline'}
-
-
-
- ${isBanned ? `
-
-
-
Banned At:
-
${device.banned_at ? formatDate(device.banned_at) : 'N/A'}
-
-
-
Banned By:
-
${escapeHtml(device.banned_by) || 'N/A'}
-
-
-
Ban Reason:
-
${escapeHtml(device.ban_reason) || 'No reason provided'}
-
- ` : ''}
-
-
Note:
-
${escapeHtml(device.note) || 'No note'}
-
-
-
Created:
-
${formatDate(device.created_at)}
-
-
-
Info:
-
${escapeHtml(device.info) || 'N/A'}
-
- `;
-
- openModal('detailsModal');
-}
-
-// Edit device
-function editDevice(deviceId) {
- const device = allDevices.find(d => d.id === deviceId);
- if (!device) return;
-
- currentDeviceId = deviceId;
- document.getElementById('editDeviceId').value = deviceId;
- document.getElementById('editNewId').value = '';
- document.getElementById('editNote').value = device.note || '';
-
- openModal('editModal');
-}
-
-// Save device changes
-async function saveDevice() {
- const newId = document.getElementById('editNewId').value.trim();
- const note = document.getElementById('editNote').value.trim();
-
- // Validation
- if (note.length > 500) {
- showToast('Note is too long (max 500 characters)', 'error');
- return;
- }
-
- if (newId && newId.length > 50) {
- showToast('Device ID is too long (max 50 characters)', 'error');
- return;
- }
-
- if (newId && !/^[a-zA-Z0-9_-]+$/.test(newId)) {
- showToast('Device ID can only contain letters, numbers, underscores and hyphens', 'error');
- return;
- }
-
- // Warning if changing ID
- if (newId && newId !== currentDeviceId) {
- if (!confirm(`⚠️ WARNING: Changing device ID!\n\nChanging the device ID may cause access issues.\nThe device will need to re-register with the new ID.\n\nOld ID: ${currentDeviceId}\nNew ID: ${newId}\n\nAre you sure you want to continue?`)) {
- return;
- }
- }
-
- const data = { note };
- if (newId) {
- data.new_id = newId;
- }
-
- try {
- const response = await fetch(`/api/device/${currentDeviceId}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data)
- });
-
- const result = await response.json();
-
- if (result.success) {
- showToast('Device updated successfully');
- closeEditModal();
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to update device', 'error');
- }
-}
-
-// Delete device
-function deleteDevice(deviceId) {
- currentDeviceId = deviceId;
- document.getElementById('deleteDeviceId').textContent = deviceId;
- openModal('deleteModal');
-}
-
-// Confirm delete
-async function confirmDelete() {
- // Additional confirmation with explicit warning
- const device = allDevices.find(d => d.id === currentDeviceId);
- const hasNote = device && device.note;
-
- const confirmMsg = `⚠️ DELETE DEVICE CONFIRMATION\n\n` +
- `Device ID: ${currentDeviceId}\n` +
- (hasNote ? `Note: ${device.note}\n` : '') +
- `Status: ${device && device.online ? 'Online' : 'Offline'}\n\n` +
- `This will remove the device from the console.\n` +
- `The device can re-register by connecting again.\n\n` +
- `Are you absolutely sure?`;
-
- if (!confirm(confirmMsg)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${currentDeviceId}`, {
- method: 'DELETE'
- });
-
- const result = await response.json();
-
- if (result.success) {
- showToast('Device deleted successfully');
- closeDeleteModal();
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to delete device', 'error');
- }
-}
-
-// Show public key modal
-function showPublicKey() {
- openModal('keyModal');
-}
-
-// Copy public key to clipboard
-function copyPublicKey() {
- const keyText = document.getElementById('publicKeyDisplay').textContent;
- navigator.clipboard.writeText(keyText).then(() => {
- showToast('Public key copied to clipboard');
- }).catch(err => {
- console.error('Error copying:', err);
- showToast('Failed to copy public key', 'error');
- });
-}
-
-// Refresh devices manually
-async function refreshDevices() {
- showToast('Refreshing devices...');
- await loadDevices();
- await loadStats();
-}
-
-// Modal functions
-function openModal(modalId) {
- document.getElementById(modalId).classList.add('active');
-}
-
-function closeModal(modalId) {
- document.getElementById(modalId).classList.remove('active');
-}
-
-function closeEditModal() {
- closeModal('editModal');
- currentDeviceId = null;
-}
-
-function closeDeleteModal() {
- closeModal('deleteModal');
- currentDeviceId = null;
-}
-
-function closeDetailsModal() {
- closeModal('detailsModal');
-}
-
-function closeKeyModal() {
- closeModal('keyModal');
-}
-
-// Close modal when clicking outside
-window.onclick = function(event) {
- if (event.target.classList.contains('modal')) {
- event.target.classList.remove('active');
- }
-}
-
-// Toast notification
-function showToast(message, type = 'success') {
- const toast = document.getElementById('toast');
- const icon = toast.querySelector('i');
-
- // Update icon based on type
- if (type === 'error') {
- icon.className = 'fas fa-exclamation-circle';
- icon.style.color = 'var(--danger-color)';
- } else {
- icon.className = 'fas fa-check-circle';
- icon.style.color = 'var(--success-color)';
- }
-
- document.getElementById('toastMessage').textContent = message;
- toast.classList.add('show');
-
- setTimeout(() => {
- toast.classList.remove('show');
- }, 3000);
-}
-
-// Utility functions
-function escapeHtml(text) {
- if (!text) return '';
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
-}
-
-function formatDate(dateString) {
- if (!dateString) return 'N/A';
- const date = new Date(dateString);
- const options = {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- };
- return date.toLocaleDateString('en-US', options);
-}
-
-// Ban device
-async function banDevice(deviceId) {
- const reason = prompt(`⚠️ BAN DEVICE: ${deviceId}\n\nEnter ban reason (optional):`);
-
- // User cancelled
- if (reason === null) {
- return;
- }
-
- const confirmMsg = `Are you sure you want to BAN device ${deviceId}?\n\n` +
- `This will:\n` +
- `- Prevent the device from connecting\n` +
- `- Block all remote access attempts\n` +
- `- Mark device as banned in the console\n\n` +
- `Ban reason: ${reason || '(no reason provided)'}\n\n` +
- `Continue?`;
-
- if (!confirm(confirmMsg)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${deviceId}/ban`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- reason: reason || '',
- banned_by: 'admin'
- })
- });
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`Device ${deviceId} banned successfully`);
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to ban device', 'error');
- }
-}
-
-// Unban device
-async function unbanDevice(deviceId) {
- const confirmMsg = `✓ UNBAN DEVICE: ${deviceId}\n\n` +
- `This will:\n` +
- `- Allow the device to connect again\n` +
- `- Remove all ban restrictions\n` +
- `- Clear ban information\n\n` +
- `Are you sure?`;
-
- if (!confirm(confirmMsg)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${deviceId}/unban`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`Device ${deviceId} unbanned successfully`);
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to unban device', 'error');
- }
-}
-
diff --git a/web/static/script_v14.js b/web/static/script_v14.js
deleted file mode 100644
index 5b3defd6..00000000
--- a/web/static/script_v14.js
+++ /dev/null
@@ -1,924 +0,0 @@
-// BetterDesk Console - Main JavaScript with Authentication v1.4.0
-// Global variables
-let allDevices = [];
-let currentDeviceId = null;
-let authToken = null;
-let userRole = null;
-
-// Initialize on page load
-document.addEventListener('DOMContentLoaded', function() {
- // Check authentication
- checkAuth();
-
- // Load data
- loadDevices();
- loadStats();
-
- // Auto-refresh every 2 seconds
- setInterval(() => {
- loadDevices();
- loadStats();
- }, 2000);
-});
-
-// Authentication check
-function checkAuth() {
- authToken = localStorage.getItem('authToken');
- userRole = localStorage.getItem('role');
-
- if (!authToken) {
- window.location.href = '/login';
- return false;
- }
-
- return true;
-}
-
-// Get auth headers for API calls
-function getAuthHeaders() {
- return {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${authToken}`
- };
-}
-
-// Handle authentication errors
-function handleAuthError(error, response) {
- if (response && response.status === 401) {
- // Token expired or invalid
- localStorage.removeItem('authToken');
- localStorage.removeItem('username');
- localStorage.removeItem('role');
- window.location.href = '/login';
- return true;
- }
- return false;
-}
-
-// Load devices from API
-async function loadDevices() {
- if (!checkAuth()) return;
-
- try {
- const response = await fetch('/api/devices', {
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const data = await response.json();
-
- if (data.success) {
- allDevices = data.devices;
- renderDevices(allDevices);
- updateNavStats(allDevices);
- } else {
- showToast('Error loading devices: ' + data.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to load devices', 'error');
- }
-}
-
-// Load statistics
-async function loadStats() {
- if (!checkAuth()) return;
-
- try {
- const response = await fetch('/api/stats', {
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const data = await response.json();
-
- if (data.success) {
- document.getElementById('statTotal').textContent = data.stats.total;
- document.getElementById('statActive').textContent = data.stats.active;
- document.getElementById('statInactive').textContent = data.stats.inactive;
- document.getElementById('statBanned').textContent = data.stats.banned || 0;
- document.getElementById('statNotes').textContent = data.stats.with_notes;
-
- // Update top bar stats
- const topTotal = document.getElementById('topTotalDevices');
- const topActive = document.getElementById('topActiveDevices');
- if (topTotal) topTotal.querySelector('span').textContent = data.stats.total;
- if (topActive) topActive.querySelector('span').textContent = data.stats.active;
- }
- } catch (error) {
- console.error('Error loading stats:', error);
- }
-}
-
-// Update navigation stats
-function updateNavStats(devices) {
- const total = devices.length;
- const active = devices.filter(d => d.online).length;
-
- const totalDevicesEl = document.querySelector('#totalDevices span');
- const activeDevicesEl = document.querySelector('#activeDevices span');
-
- if (totalDevicesEl) totalDevicesEl.textContent = total;
- if (activeDevicesEl) activeDevicesEl.textContent = active;
-}
-
-// Render devices table
-function renderDevices(devices) {
- const tbody = document.getElementById('devicesTableBody');
-
- if (devices.length === 0) {
- tbody.innerHTML = `
-
- |
-
- No devices found
- |
-
- `;
- return;
- }
-
- tbody.innerHTML = devices.map(device => {
- const isBanned = device.is_banned === true || device.is_banned === 1;
- const rowClass = isBanned ? 'style="opacity: 0.6; background: rgba(255, 0, 0, 0.05);"' : '';
-
- // Check permissions for actions
- const canEdit = userRole === 'admin' || userRole === 'operator';
- const canBan = userRole === 'admin' || userRole === 'operator';
-
- return `
-
-
- ${escapeHtml(device.id)}
- ${isBanned ? ' BANNED' : ''}
- |
- ${escapeHtml(device.note) || 'No note'} |
-
-
-
- ${device.online ? 'Online' : 'Offline'}
-
- |
- ${formatDate(device.created_at)} |
-
-
-
- ${canEdit ? `
-
- ` : ''}
- ${canBan ? (isBanned ?
- `` :
- ``
- ) : ''}
- ${canEdit ? `
-
- ` : ''}
- |
-
- `}).join('');
-}
-
-// Filter devices by search
-function filterDevices() {
- const searchTerm = document.getElementById('searchInput').value.toLowerCase();
-
- if (!searchTerm) {
- renderDevices(allDevices);
- return;
- }
-
- const filtered = allDevices.filter(device =>
- device.id.toLowerCase().includes(searchTerm) ||
- (device.note && device.note.toLowerCase().includes(searchTerm))
- );
-
- renderDevices(filtered);
-}
-
-// Connect to device via rustdesk:// protocol
-function connectDevice(deviceId) {
- window.location.href = `rustdesk://${deviceId}`;
- showToast(`Connecting to ${deviceId}...`);
-}
-
-// Show device details modal
-function showDetails(deviceId) {
- const device = allDevices.find(d => d.id === deviceId);
- if (!device) return;
-
- const isBanned = device.is_banned === true || device.is_banned === 1;
-
- const detailsContent = document.getElementById('detailsContent');
- detailsContent.innerHTML = `
-
-
ID:
-
${escapeHtml(device.id)}
-
-
-
GUID:
-
${escapeHtml(device.guid) || 'N/A'}
-
-
-
UUID:
-
${escapeHtml(device.uuid) || 'N/A'}
-
-
-
Public Key:
-
${escapeHtml(device.pk) || 'N/A'}
-
-
-
User:
-
${escapeHtml(device.user) || 'N/A'}
-
-
-
Status:
-
-
-
- ${device.online ? 'Online' : 'Offline'}
-
-
-
- ${isBanned ? `
-
-
-
Banned At:
-
${device.banned_at ? formatDate(device.banned_at) : 'N/A'}
-
-
-
Banned By:
-
${escapeHtml(device.banned_by) || 'N/A'}
-
-
-
Ban Reason:
-
${escapeHtml(device.ban_reason) || 'No reason provided'}
-
- ` : ''}
-
-
Note:
-
${escapeHtml(device.note) || 'No note'}
-
-
-
Created:
-
${formatDate(device.created_at)}
-
-
-
Info:
-
${escapeHtml(device.info) || 'N/A'}
-
- `;
-
- openModal('detailsModal');
-}
-
-// Edit device
-function editDevice(deviceId) {
- const device = allDevices.find(d => d.id === deviceId);
- if (!device) return;
-
- currentDeviceId = deviceId;
- document.getElementById('editDeviceId').value = deviceId;
- document.getElementById('editNewId').value = '';
- document.getElementById('editNote').value = device.note || '';
-
- openModal('editModal');
-}
-
-// Save device changes
-async function saveDevice() {
- if (!checkAuth()) return;
-
- const newId = document.getElementById('editNewId').value.trim();
- const note = document.getElementById('editNote').value.trim();
-
- if (note.length > 500) {
- showToast('Note is too long (max 500 characters)', 'error');
- return;
- }
-
- if (newId && newId.length > 50) {
- showToast('Device ID is too long (max 50 characters)', 'error');
- return;
- }
-
- if (newId && !/^[a-zA-Z0-9_-]+$/.test(newId)) {
- showToast('Device ID can only contain letters, numbers, underscores and hyphens', 'error');
- return;
- }
-
- if (newId && newId !== currentDeviceId) {
- if (!confirm(`⚠️ WARNING: Changing device ID!\n\nOld ID: ${currentDeviceId}\nNew ID: ${newId}\n\nAre you sure?`)) {
- return;
- }
- }
-
- const data = { note };
- if (newId) data.new_id = newId;
-
- try {
- const response = await fetch(`/api/device/${currentDeviceId}`, {
- method: 'PUT',
- headers: getAuthHeaders(),
- body: JSON.stringify(data)
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast('Device updated successfully');
- closeEditModal();
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to update device', 'error');
- }
-}
-
-// Delete device
-function deleteDevice(deviceId) {
- currentDeviceId = deviceId;
- document.getElementById('deleteDeviceId').textContent = deviceId;
- openModal('deleteModal');
-}
-
-// Confirm delete
-async function confirmDelete() {
- if (!checkAuth()) return;
-
- const device = allDevices.find(d => d.id === currentDeviceId);
-
- if (!confirm(`⚠️ DELETE DEVICE: ${currentDeviceId}\n\nAre you absolutely sure?`)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${currentDeviceId}`, {
- method: 'DELETE',
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast('Device deleted successfully');
- closeDeleteModal();
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to delete device', 'error');
- }
-}
-
-// Copy public key to clipboard
-function copyPublicKey() {
- const keyText = document.getElementById('publicKeyDisplay').textContent;
- navigator.clipboard.writeText(keyText).then(() => {
- showToast('Public key copied to clipboard');
- }).catch(err => {
- console.error('Error copying:', err);
- showToast('Failed to copy public key', 'error');
- });
-}
-
-// Refresh devices manually
-async function refreshDevices() {
- showToast('Refreshing devices...');
- await loadDevices();
- await loadStats();
-}
-
-// Modal functions
-function openModal(modalId) {
- document.getElementById(modalId).classList.add('active');
-}
-
-function closeModal(modalId) {
- document.getElementById(modalId).classList.remove('active');
-}
-
-function closeEditModal() {
- closeModal('editModal');
- currentDeviceId = null;
-}
-
-function closeDeleteModal() {
- closeModal('deleteModal');
- currentDeviceId = null;
-}
-
-function closeDetailsModal() {
- closeModal('detailsModal');
-}
-
-// Close modal when clicking outside
-window.onclick = function(event) {
- if (event.target.classList.contains('modal')) {
- event.target.classList.remove('active');
- }
-}
-
-// Toast notification
-function showToast(message, type = 'success') {
- const toast = document.getElementById('toast');
- const icon = toast.querySelector('i');
-
- if (type === 'error') {
- icon.className = 'fas fa-exclamation-circle';
- icon.style.color = 'var(--danger-color)';
- } else {
- icon.className = 'fas fa-check-circle';
- icon.style.color = 'var(--success-color)';
- }
-
- document.getElementById('toastMessage').textContent = message;
- toast.classList.add('show');
-
- setTimeout(() => {
- toast.classList.remove('show');
- }, 3000);
-}
-
-// Utility functions
-function escapeHtml(text) {
- if (!text) return '';
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
-}
-
-function formatDate(dateString) {
- if (!dateString) return 'N/A';
- const date = new Date(dateString);
- const options = {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit'
- };
- return date.toLocaleDateString('en-US', options);
-}
-
-// Ban device
-async function banDevice(deviceId) {
- if (!checkAuth()) return;
-
- const reason = prompt(`⚠️ BAN DEVICE: ${deviceId}\n\nEnter ban reason (optional):`);
-
- if (reason === null) return;
-
- if (reason && reason.length > 500) {
- showToast('Ban reason is too long (max 500 characters)', 'error');
- return;
- }
-
- if (!confirm(`Are you sure you want to BAN device ${deviceId}?`)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${deviceId}/ban`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({
- reason: reason || '',
- banned_by: 'admin'
- })
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`Device ${deviceId} banned successfully`);
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to ban device', 'error');
- }
-}
-
-// Unban device
-async function unbanDevice(deviceId) {
- if (!checkAuth()) return;
-
- if (!confirm(`✓ UNBAN DEVICE: ${deviceId}\n\nAre you sure?`)) {
- return;
- }
-
- try {
- const response = await fetch(`/api/device/${deviceId}/unban`, {
- method: 'POST',
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`Device ${deviceId} unbanned successfully`);
- await loadDevices();
- await loadStats();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to unban device', 'error');
- }
-}
-// ============================================================================
-// PUBLIC KEY VERIFICATION
-// ============================================================================
-
-async function verifyPasswordForKey() {
- const password = document.getElementById('keyPassword').value;
-
- if (!password) {
- showToast('Please enter your password', 'error');
- return;
- }
-
- try {
- const response = await fetch('/api/key/verify', {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ password: password })
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- document.getElementById('publicKeyDisplay').textContent = result.key;
- document.getElementById('keyPasswordForm').style.display = 'none';
- document.getElementById('keyDisplay').style.display = 'block';
- document.getElementById('keyPassword').value = '';
- showToast('Public key revealed');
- } else {
- showToast('Error: ' + result.error, 'error');
- document.getElementById('keyPassword').value = '';
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to verify password', 'error');
- }
-}
-
-function copyPublicKey() {
- const keyText = document.getElementById('publicKeyDisplay').textContent;
- navigator.clipboard.writeText(keyText).then(() => {
- showToast('Public key copied to clipboard');
- }).catch(err => {
- showToast('Failed to copy', 'error');
- });
-}
-
-// ============================================================================
-// PASSWORD CHANGE
-// ============================================================================
-
-function showChangePasswordModal() {
- document.getElementById('changePasswordModal').classList.add('show');
-}
-
-function closeChangePasswordModal() {
- document.getElementById('changePasswordModal').classList.remove('show');
- document.getElementById('currentPassword').value = '';
- document.getElementById('newPassword').value = '';
- document.getElementById('confirmPassword').value = '';
-}
-
-async function confirmChangePassword() {
- const currentPassword = document.getElementById('currentPassword').value;
- const newPassword = document.getElementById('newPassword').value;
- const confirmPassword = document.getElementById('confirmPassword').value;
-
- if (!currentPassword || !newPassword || !confirmPassword) {
- showToast('All fields are required', 'error');
- return;
- }
-
- if (newPassword.length < 6) {
- showToast('New password must be at least 6 characters', 'error');
- return;
- }
-
- if (newPassword !== confirmPassword) {
- showToast('New passwords do not match', 'error');
- return;
- }
-
- try {
- const response = await fetch('/api/auth/change-password', {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({
- old_password: currentPassword,
- new_password: newPassword
- })
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast('Password changed successfully');
- closeChangePasswordModal();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to change password', 'error');
- }
-}
-
-// ============================================================================
-// USER MANAGEMENT
-// ============================================================================
-
-async function loadUsers() {
- if (!checkAuth()) return;
-
- try {
- const response = await fetch('/api/users', {
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const data = await response.json();
-
- if (data.success) {
- renderUsers(data.users);
- } else {
- showToast('Error loading users: ' + data.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to load users', 'error');
- }
-}
-
-function renderUsers(users) {
- const tbody = document.getElementById('usersTableBody');
-
- if (users.length === 0) {
- tbody.innerHTML = '| No users found |
';
- return;
- }
-
- tbody.innerHTML = users.map(user => {
- const statusBadge = user.is_active ?
- 'Active' :
- 'Inactive';
-
- const roleColor = user.role === 'admin' ? 'danger' :
- user.role === 'operator' ? 'warning' : 'info';
- const roleBadge = `${user.role}`;
-
- const createdDate = user.created_at ? new Date(user.created_at).toLocaleDateString() : 'N/A';
- const lastLogin = user.last_login ? new Date(user.last_login).toLocaleString() : 'Never';
-
- return `
-
- | ${user.username} |
- ${roleBadge} |
- ${statusBadge} |
- ${createdDate} |
- ${lastLogin} |
-
-
-
- ${user.is_active ?
- `` :
- ``
- }
- |
-
- `;
- }).join('');
-}
-
-// Add User Modal
-function showAddUserModal() {
- document.getElementById('addUserModal').classList.add('show');
-}
-
-function closeAddUserModal() {
- document.getElementById('addUserModal').classList.remove('show');
- document.getElementById('newUsername').value = '';
- document.getElementById('newUserPassword').value = '';
- document.getElementById('newUserRole').value = 'viewer';
-}
-
-async function confirmAddUser() {
- const username = document.getElementById('newUsername').value.trim();
- const password = document.getElementById('newUserPassword').value;
- const role = document.getElementById('newUserRole').value;
-
- if (!username || !password) {
- showToast('Username and password are required', 'error');
- return;
- }
-
- if (password.length < 6) {
- showToast('Password must be at least 6 characters', 'error');
- return;
- }
-
- try {
- const response = await fetch('/api/users', {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ username, password, role })
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`User ${username} created successfully`);
- closeAddUserModal();
- loadUsers();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to create user', 'error');
- }
-}
-
-// Edit User Modal
-function showEditUserModal(userId, username, role) {
- document.getElementById('editUserId').value = userId;
- document.getElementById('editUserUsername').value = username;
- document.getElementById('editUserRole').value = role;
- document.getElementById('resetUserPassword').value = '';
- document.getElementById('editUserModal').classList.add('show');
-}
-
-function closeEditUserModal() {
- document.getElementById('editUserModal').classList.remove('show');
-}
-
-async function confirmEditUser() {
- const userId = document.getElementById('editUserId').value;
- const role = document.getElementById('editUserRole').value;
- const password = document.getElementById('resetUserPassword').value;
-
- try {
- // Change role
- const roleResponse = await fetch(`/api/users/${userId}`, {
- method: 'PUT',
- headers: getAuthHeaders(),
- body: JSON.stringify({ action: 'change_role', role })
- });
-
- if (handleAuthError(null, roleResponse)) return;
-
- const roleResult = await roleResponse.json();
-
- if (!roleResult.success) {
- showToast('Error: ' + roleResult.error, 'error');
- return;
- }
-
- // Reset password if provided
- if (password && password.length >= 6) {
- const passResponse = await fetch(`/api/users/${userId}`, {
- method: 'PUT',
- headers: getAuthHeaders(),
- body: JSON.stringify({ action: 'reset_password', password })
- });
-
- const passResult = await passResponse.json();
-
- if (!passResult.success) {
- showToast('Role updated but password reset failed: ' + passResult.error, 'error');
- closeEditUserModal();
- loadUsers();
- return;
- }
- }
-
- showToast('User updated successfully');
- closeEditUserModal();
- loadUsers();
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to update user', 'error');
- }
-}
-
-// Delete User Modal
-function showDeleteUserModal(userId, username) {
- document.getElementById('deleteUserId').value = userId;
- document.getElementById('deleteUserUsername').textContent = username;
- document.getElementById('deleteUserModal').classList.add('show');
-}
-
-function closeDeleteUserModal() {
- document.getElementById('deleteUserModal').classList.remove('show');
-}
-
-async function confirmDeleteUser() {
- const userId = document.getElementById('deleteUserId').value;
-
- try {
- const response = await fetch(`/api/users/${userId}`, {
- method: 'DELETE',
- headers: getAuthHeaders()
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast('User deleted successfully');
- closeDeleteUserModal();
- loadUsers();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to delete user', 'error');
- }
-}
-
-// Toggle User Status
-async function toggleUserStatus(userId, activate) {
- const action = activate ? 'activate' : 'deactivate';
-
- try {
- const response = await fetch(`/api/users/${userId}`, {
- method: 'PUT',
- headers: getAuthHeaders(),
- body: JSON.stringify({ action })
- });
-
- if (handleAuthError(null, response)) return;
-
- const result = await response.json();
-
- if (result.success) {
- showToast(`User ${activate ? 'activated' : 'deactivated'} successfully`);
- loadUsers();
- } else {
- showToast('Error: ' + result.error, 'error');
- }
- } catch (error) {
- console.error('Error:', error);
- showToast('Failed to change user status', 'error');
- }
-}
\ No newline at end of file
diff --git a/web/static/sidebar.css b/web/static/sidebar.css
deleted file mode 100644
index 5e5ad04e..00000000
--- a/web/static/sidebar.css
+++ /dev/null
@@ -1,412 +0,0 @@
-/* Sidebar Styles for BetterDesk Console v1.4.0 */
-
-:root {
- --sidebar-width: 280px;
-}
-
-/* Layout adjustments for sidebar */
-body.has-sidebar {
- display: flex;
- min-height: 100vh;
-}
-
-/* Sidebar Container - Zoptymalizowany dla GPU */
-.sidebar {
- position: fixed;
- left: 0;
- top: 0;
- bottom: 0;
- width: var(--sidebar-width);
- background: rgba(20, 20, 20, 0.95);
- /* backdrop-filter disabled for better performance */
- border-right: 1px solid rgba(255, 255, 255, 0.1);
- display: flex;
- flex-direction: column;
- z-index: 1000;
- overflow-y: auto;
-}
-
-/* Sidebar Header */
-.sidebar-header {
- padding: 24px 20px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.sidebar-brand {
- display: flex;
- align-items: center;
- gap: 12px;
- color: white;
- font-size: 20px;
- font-weight: 700;
- white-space: nowrap;
-}
-
-.sidebar-brand i {
- font-size: 28px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.brand-text {
- /* Text always visible */
-}
-
-/* Toggle button removed - sidebar always expanded */
-
-/* Sidebar User Section */
-.sidebar-user {
- padding: 20px;
- display: flex;
- align-items: center;
- gap: 12px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.user-avatar {
- width: 48px;
- height: 48px;
- border-radius: 12px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-size: 24px;
- flex-shrink: 0;
-}
-
-.user-info {
- flex: 1;
- min-width: 0;
- white-space: nowrap;
- overflow: hidden;
-}
-
-.user-name {
- color: white;
- font-weight: 600;
- font-size: 14px;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.user-role {
- color: rgba(255, 255, 255, 0.7);
- font-size: 12px;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-/* Sidebar Menu */
-.sidebar-menu {
- flex: 1;
- padding: 12px;
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-.sidebar-menu::-webkit-scrollbar {
- width: 4px;
-}
-
-.sidebar-menu::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.2);
- border-radius: 4px;
-}
-
-.menu-item {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 16px;
- color: rgba(255, 255, 255, 0.8);
- text-decoration: none;
- border-radius: 12px;
- transition: all 0.3s;
- margin-bottom: 4px;
- cursor: pointer;
- position: relative;
- white-space: nowrap;
-}
-
-.menu-item i {
- font-size: 18px;
- width: 20px;
- text-align: center;
- flex-shrink: 0;
-}
-
-.menu-item span {
- font-size: 14px;
- font-weight: 500;
-}
-
-.menu-item:hover {
- background: rgba(255, 255, 255, 0.1);
- color: white;
- transform: translateX(4px);
-}
-
-.menu-item.active {
- background: rgba(255, 255, 255, 0.15);
- color: white;
- font-weight: 600;
-}
-
-.menu-item.active::before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 4px;
- height: 60%;
- background: white;
- border-radius: 0 4px 4px 0;
-}
-
-/* Sidebar Footer */
-.sidebar-footer {
- padding: 12px;
- border-top: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.logout-btn {
- color: #ff6b6b !important;
- justify-content: flex-start;
-}
-
-.logout-btn:hover {
- background: rgba(255, 107, 107, 0.1) !important;
-}
-
-/* Main Content Area */
-.main-content {
- flex: 1;
- margin-left: var(--sidebar-width);
- display: flex;
- flex-direction: column;
- min-height: 100vh;
-}
-
-/* Top Navbar */
-.top-navbar {
- position: sticky;
- top: 0;
- z-index: 999;
- background: rgba(20, 20, 20, 0.95);
- /* backdrop-filter disabled for better performance */
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
- padding: 16px 24px;
-}
-
-.nav-content {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 20px;
-}
-
-.mobile-menu-toggle {
- display: none;
- background: rgba(255, 255, 255, 0.1);
- border: none;
- color: white;
- width: 40px;
- height: 40px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 18px;
-}
-
-.page-title {
- color: white;
- font-size: 24px;
- font-weight: 700;
- margin: 0;
- flex: 1;
-}
-
-.nav-stats {
- display: flex;
- gap: 12px;
-}
-
-.stat-badge {
- background: rgba(255, 255, 255, 0.1);
- padding: 8px 16px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- gap: 8px;
- color: white;
-}
-
-.stat-badge i {
- font-size: 16px;
-}
-
-.stat-badge.active {
- background: rgba(76, 175, 80, 0.2);
- color: #4caf50;
-}
-
-/* Content Container */
-.content-container {
- flex: 1;
- padding: 24px;
- overflow-y: auto;
-}
-
-.page-content {
- display: none;
-}
-
-.page-content.active {
- display: block;
- animation: fadeIn 0.3s ease;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translate3d(0, 10px, 0);
- }
- to {
- opacity: 1;
- transform: translate3d(0, 0, 0);
- }
-}
-
-/* Action Bar */
-.action-bar {
- margin-bottom: 24px;
- display: flex;
- gap: 12px;
- justify-content: flex-end;
-}
-
-/* Responsive Design */
-@media (max-width: 1024px) {
- :root {
- --sidebar-width: 260px;
- }
-}
-
-@media (max-width: 768px) {
- .sidebar {
- transform: translateX(-100%);
- }
-
- .sidebar.mobile-open {
- transform: translateX(0);
- }
-
- .main-content {
- margin-left: 0 !important;
- }
-
- .mobile-menu-toggle {
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .page-title {
- font-size: 20px;
- }
-
- .nav-stats {
- display: none;
- }
-
- .content-container {
- padding: 16px;
- }
-
- /* Overlay when sidebar is open on mobile */
- .sidebar-overlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 999;
- display: none;
- }
-
- .sidebar.mobile-open ~ .sidebar-overlay {
- display: block;
- }
-}
-
-/* Settings Page Styles */
-.settings-container {
- padding: 24px;
- border-radius: 16px;
-}
-
-.settings-section {
- margin-bottom: 24px;
-}
-
-.settings-section h3 {
- color: white;
- margin-bottom: 16px;
-}
-
-/* Key Container */
-.key-container {
- padding: 24px;
- border-radius: 16px;
-}
-
-.key-display {
- background: rgba(0, 0, 0, 0.2);
- padding: 16px;
- border-radius: 12px;
- margin: 16px 0;
- word-break: break-all;
-}
-
-.key-display code {
- color: #4caf50;
- font-family: 'Courier New', monospace;
-}
-
-/* About Container */
-.about-container {
- padding: 24px;
- border-radius: 16px;
- color: white;
-}
-
-.about-container h2 {
- margin-bottom: 20px;
-}
-
-.about-container p {
- margin: 12px 0;
-}
-
-.about-container ul {
- list-style: none;
- padding-left: 0;
-}
-
-.about-container li {
- padding: 8px 0;
- padding-left: 24px;
- position: relative;
-}
-
-.about-container li::before {
- content: '✓';
- position: absolute;
- left: 0;
- color: #4caf50;
-}
diff --git a/web/static/sidebar.js b/web/static/sidebar.js
deleted file mode 100644
index 3819b75d..00000000
--- a/web/static/sidebar.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/* Sidebar JavaScript for BetterDesk Console v1.4.0 */
-
-// Initialize sidebar
-document.addEventListener('DOMContentLoaded', function() {
- initializeSidebar();
- loadUserInfo();
- setupMenuNavigation();
- setupMobileMenu();
-});
-
-function initializeSidebar() {
- // Sidebar is always expanded - no toggle needed
- console.log('Sidebar initialized (always expanded)');
-}
-
-function loadUserInfo() {
- const username = localStorage.getItem('username') || 'User';
- const role = localStorage.getItem('role') || 'viewer';
-
- // Update sidebar user info
- const usernameEl = document.getElementById('sidebarUsername');
- const userRoleEl = document.getElementById('sidebarUserRole');
-
- if (usernameEl) {
- usernameEl.textContent = username;
- }
-
- if (userRoleEl) {
- const roleNames = {
- 'admin': 'Administrator',
- 'operator': 'Operator',
- 'viewer': 'Viewer'
- };
- userRoleEl.textContent = roleNames[role] || role;
- }
-
- // Show/hide menu items based on role
- updateMenuVisibility(role);
-}
-
-function updateMenuVisibility(role) {
- const menuUsers = document.getElementById('menuUsers');
- const menuAudit = document.getElementById('menuAudit');
- const menuSettings = document.getElementById('menuSettings');
- const menuKey = document.getElementById('menuKey');
-
- // Admin sees everything
- if (role === 'admin') {
- if (menuUsers) menuUsers.style.display = 'flex';
- if (menuAudit) menuAudit.style.display = 'flex';
- if (menuSettings) menuSettings.style.display = 'flex';
- if (menuKey) menuKey.style.display = 'flex';
- }
- // Operator sees audit and settings
- else if (role === 'operator') {
- if (menuUsers) menuUsers.style.display = 'none';
- if (menuAudit) menuAudit.style.display = 'flex';
- if (menuSettings) menuSettings.style.display = 'flex';
- if (menuKey) menuKey.style.display = 'none';
- }
- // Viewer sees only settings
- else {
- if (menuUsers) menuUsers.style.display = 'none';
- if (menuAudit) menuAudit.style.display = 'none';
- if (menuSettings) menuSettings.style.display = 'flex';
- if (menuKey) menuKey.style.display = 'none';
- }
-}
-
-function setupMenuNavigation() {
- const menuItems = document.querySelectorAll('.menu-item[data-page]');
-
- menuItems.forEach(item => {
- item.addEventListener('click', function(e) {
- e.preventDefault();
-
- const page = this.dataset.page;
-
- // Update active menu item
- menuItems.forEach(mi => mi.classList.remove('active'));
- this.classList.add('active');
-
- // Show corresponding page
- showPage(page);
-
- // Close mobile menu if open
- closeMobileMenu();
- });
- });
-}
-
-function showPage(pageName) {
- // Hide all pages
- const pages = document.querySelectorAll('.page-content');
- pages.forEach(page => page.classList.remove('active'));
-
- // Show selected page
- const targetPage = document.getElementById(pageName + 'Page');
- if (targetPage) {
- targetPage.classList.add('active');
- }
-
- // Update page title
- const pageTitles = {
- 'dashboard': 'Device Management',
- 'users': 'User Management',
- 'audit': 'Audit Log',
- 'settings': 'Settings',
- 'key': 'Public Key',
- 'about': 'About BetterDesk'
- };
-
- const pageTitle = document.getElementById('pageTitle');
- if (pageTitle && pageTitles[pageName]) {
- pageTitle.textContent = pageTitles[pageName];
- }
-
- // Load page-specific data
- if (pageName === 'dashboard') {
- if (typeof refreshDevices === 'function') {
- refreshDevices();
- }
- } else if (pageName === 'users') {
- if (typeof loadUsers === 'function') {
- loadUsers();
- }
- }
-}
-
-function setupMobileMenu() {
- const mobileToggle = document.getElementById('mobileMenuToggle');
- const sidebar = document.getElementById('sidebar');
-
- if (mobileToggle) {
- mobileToggle.addEventListener('click', function() {
- sidebar.classList.toggle('mobile-open');
-
- // Create/remove overlay
- if (sidebar.classList.contains('mobile-open')) {
- createOverlay();
- } else {
- removeOverlay();
- }
- });
- }
-}
-
-function createOverlay() {
- const existing = document.querySelector('.sidebar-overlay');
- if (existing) return;
-
- const overlay = document.createElement('div');
- overlay.className = 'sidebar-overlay';
- overlay.addEventListener('click', closeMobileMenu);
- document.body.appendChild(overlay);
-}
-
-function removeOverlay() {
- const overlay = document.querySelector('.sidebar-overlay');
- if (overlay) {
- overlay.remove();
- }
-}
-
-function closeMobileMenu() {
- const sidebar = document.getElementById('sidebar');
- sidebar.classList.remove('mobile-open');
- removeOverlay();
-}
-
-async function logout() {
- if (!confirm('Are you sure you want to logout?')) {
- return;
- }
-
- const token = localStorage.getItem('authToken');
-
- // Call logout API
- try {
- await fetch('/api/auth/logout', {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- });
- } catch (error) {
- console.error('Logout error:', error);
- }
-
- // Clear local storage
- localStorage.removeItem('authToken');
- localStorage.removeItem('username');
- localStorage.removeItem('role');
-
- // Redirect to login
- window.location.href = '/login';
-}
-
-function showChangePasswordModal() {
- alert('Change password functionality coming soon!');
-}
-
-// Export functions for use in other scripts
-window.sidebarFunctions = {
- showPage,
- logout,
- loadUserInfo,
- updateMenuVisibility
-};
diff --git a/web/templates/client_generator.html b/web/templates/client_generator.html
index 2db772bb..d63f78b3 100644
--- a/web/templates/client_generator.html
+++ b/web/templates/client_generator.html
@@ -3,87 +3,199 @@
+
+
+
BetterDesk Console - Client Generator
-
-
-
+
+
+
-
+
-