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 @@ ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![RustDesk](https://img.shields.io/badge/RustDesk-1.1.14-green.svg) ![Python](https://img.shields.io/badge/Python-3.8+-blue.svg) -![Version](https://img.shields.io/badge/version-1.5.0-brightgreen.svg) +![Version](https://img.shields.io/badge/version-1.5.1-brightgreen.svg) ![Security](https://img.shields.io/badge/API-X--API--Key--Auth-green.svg) ![Access](https://img.shields.io/badge/LAN-Accessible-blue.svg) @@ -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 ? ` -
-
BAN STATUS:
-
BANNED
-
-
-
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 ? ` -
-
BAN STATUS:
-
BANNED
-
-
-
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 - - - + + + - +
- +
- - + +
+

+ + RustDesk Custom Client Builder +

+

Generate custom RustDesk clients with your server configuration

+
@@ -541,7 +653,6 @@
- - + diff --git a/web/templates/index_v14.html b/web/templates/index_v14.html deleted file mode 100644 index 7bcc11fd..00000000 --- a/web/templates/index_v14.html +++ /dev/null @@ -1,536 +0,0 @@ - - - - - - BetterDesk Console - Dashboard - - - - - - -
- - - - - -
- - - - -
- -
- -
- -
- - -
-
-
- -
-
-
Total Devices
-
0
-
-
- -
-
- -
-
-
Active
-
0
-
-
- -
-
- -
-
-
Inactive
-
0
-
-
- -
-
- -
-
-
Banned
-
0
-
-
- -
-
- -
-
-
With Notes
-
0
-
-
-
- - -
-
-

Devices

- -
- -
- - - - - - - - - - - - - - - -
IDNoteStatusCreatedActions
-
- Loading devices... -
-
-
-
- - -
-

Devices management page (to be implemented)

-
- -
-
-

User Management

-
- -
-
- - - - - - - - - - - - - - - - -
UsernameRoleStatusCreatedLast LoginActions
Loading users...
-
-
-
- -
-
-

Audit Log

-

Audit log page (to be implemented)

-
-
- -
-
-

Settings

-
-

Account Security

-

Change your account password to keep your account secure.

- -
-
-
- -
-
-

RustDesk Public Key

-

For security reasons, please verify your password to view the public key.

-
- - - -
- -
-
- -
-
-

About BetterDesk Console

- -
-

Version Information

-

Version: 1.4.0

-

Build: v9

-
- -
-

Features

-
    -
  • Real-time device monitoring and management
  • -
  • Bidirectional ban enforcement (source + target)
  • -
  • User authentication & role-based access control
  • -
  • Comprehensive audit logging
  • -
  • HTTP API for device status
  • -
  • Modern glassmorphism UI design
  • -
-
- -
-

Built With Open Source

-

This project is built using the following open source technologies:

-
    -
  • RustDesk - Open source remote desktop software (AGPL-3.0)
  • -
  • Flask - Python web framework (BSD-3-Clause)
  • -
  • SQLite - Embedded database (Public Domain)
  • -
  • bcrypt - Password hashing library (Apache-2.0)
  • -
  • Font Awesome - Icon library (Font Awesome Free License)
  • -
-
- -
-

Repository & Documentation

-

GitHub Repository:

-

- - github.com/UNITRONIX/Rustdesk-FreeConsole - -

-

- Find documentation, installation guides, and source code in the repository. -

-
- -
-

License

-

MIT License - Free to use and modify

-
- -
-

Author

-

Developed by UNITRONIX

-

With contributions from the open source community

-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
- - -
- - - - - diff --git a/web/templates/index_v15.html b/web/templates/index_v15.html index 6888aa5e..37b2a2df 100644 --- a/web/templates/index_v15.html +++ b/web/templates/index_v15.html @@ -3,8 +3,11 @@ + + + BetterDesk Console v1.5 - +