diff --git a/CyberCP/settings.py b/CyberCP/settings.py index ad059c6a7..91d6b1353 100644 --- a/CyberCP/settings.py +++ b/CyberCP/settings.py @@ -107,6 +107,7 @@ TEMPLATES = [ 'django.contrib.messages.context_processors.messages', 'baseTemplate.context_processors.version_context', 'baseTemplate.context_processors.cosmetic_context', + 'baseTemplate.context_processors.notification_preferences_context', ], }, }, diff --git a/README.md b/README.md index 2cf210db0..814159cb3 100755 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ CyberPanel comes with comprehensive documentation and step-by-step guides: - 🐳 **[Docker Command Execution](guides/Docker_Command_Execution_Guide.md)** - Execute commands in Docker containers - 🤖 **[AI Scanner Setup](guides/AIScannerDocs.md)** - Configure AI-powered security scanning - 📧 **[Mautic Installation](guides/MAUTIC_INSTALLATION_GUIDE.md)** - Email marketing platform setup +- 🎨 **[Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md)** - Create custom themes for CyberPanel 2.5.5-dev --- @@ -109,7 +110,6 @@ Install CyberPanel easily with the following command: sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh) ``` - --- ## 📊 Upgrading CyberPanel @@ -125,6 +125,7 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr ## 🆕 Recent Updates & Fixes ### **Bandwidth Reset Issue Fixed** (January 2025) + - **Issue**: Monthly bandwidth usage was not resetting, causing cumulative values to grow indefinitely - **Solution**: Implemented automatic monthly bandwidth reset for all websites and child domains - **Affected OS**: All supported operating systems (Ubuntu, AlmaLinux, RockyLinux, RHEL, CloudLinux, CentOS) @@ -132,6 +133,7 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr - **Documentation**: See [Bandwidth Reset Fix Guide](to-do/cyberpanel-bandwidth-reset-fix.md) ### **New Operating System Support Added** (January 2025) + - **Ubuntu 24.04.3**: Full compatibility with latest Ubuntu LTS - **AlmaLinux 10**: Full compatibility with latest AlmaLinux release - **Long-term Support**: Both supported until 2029-2030 @@ -157,17 +159,19 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr - 🐳 [Docker Command Execution](guides/Docker_Command_Execution_Guide.md) - Execute commands in Docker containers - 🤖 [AI Scanner Setup](guides/AIScannerDocs.md) - Configure AI-powered security scanning - 📧 [Mautic Installation](guides/MAUTIC_INSTALLATION_GUIDE.md) - Email marketing platform setup +- 🎨 [Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md) - Create custom themes for CyberPanel 2.5.5+ - 📚 [All Guides Index](guides/INDEX.md) - Complete documentation hub ### 🔗 **Direct Guide Links** -| Feature | Guide | Description | -| ----------- | ---------------------------------------------------------- | ------------------------------ | -| 🐳 Docker | [Command Execution](guides/Docker_Command_Execution_Guide.md) | Execute commands in containers | -| 🤖 Security | [AI Scanner](guides/AIScannerDocs.md) | AI-powered security scanning | -| 📧 Email | [Mautic Setup](guides/MAUTIC_INSTALLATION_GUIDE.md) | Email marketing platform | -| 📊 Bandwidth | [Reset Fix Guide](to-do/cyberpanel-bandwidth-reset-fix.md) | Fix bandwidth reset issues | -| 📚 All | [Complete Index](guides/INDEX.md) | Browse all available guides | +| Feature | Guide | Description | +| ------------ | ---------------------------------------------------------- | ---------------------------------- | +| 🐳 Docker | [Command Execution](guides/Docker_Command_Execution_Guide.md) | Execute commands in containers | +| 🤖 Security | [AI Scanner](guides/AIScannerDocs.md) | AI-powered security scanning | +| 📧 Email | [Mautic Setup](guides/MAUTIC_INSTALLATION_GUIDE.md) | Email marketing platform | +| 🎨 Design | [Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md) | Create custom themes for 2.5.5-dev | +| 📊 Bandwidth | [Reset Fix Guide](to-do/cyberpanel-bandwidth-reset-fix.md) | Fix bandwidth reset issues | +| 📚 All | [Complete Index](guides/INDEX.md) | Browse all available guides | --- @@ -176,12 +180,13 @@ sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgr ### **Common Issues & Solutions** #### **Bandwidth Not Resetting Monthly** + - **Issue**: Bandwidth usage shows cumulative values instead of monthly usage - **Solution**: Run the bandwidth reset script: `/usr/local/CyberCP/scripts/reset_bandwidth.sh` - **Prevention**: Ensure monthly cron job is running: `0 0 1 * * /usr/local/CyberCP/bin/python /usr/local/CyberCP/postfixSenderPolicy/client.py monthlyCleanup` - #### **General Support** + - Check logs: `/usr/local/lscp/logs/error.log` - Verify cron jobs: `crontab -l` - Test manual reset: Use provided scripts in `/usr/local/CyberCP/scripts/` diff --git a/baseTemplate/apps.py b/baseTemplate/apps.py index f5ce2e6d1..773d21648 100644 --- a/baseTemplate/apps.py +++ b/baseTemplate/apps.py @@ -6,3 +6,6 @@ from django.apps import AppConfig class BasetemplateConfig(AppConfig): name = 'baseTemplate' + + def ready(self): + import baseTemplate.signals \ No newline at end of file diff --git a/baseTemplate/context_processors.py b/baseTemplate/context_processors.py index 13d871c10..696f9d840 100644 --- a/baseTemplate/context_processors.py +++ b/baseTemplate/context_processors.py @@ -23,4 +23,30 @@ def cosmetic_context(request): cosmetic.save() return { 'cosmetic': cosmetic - } \ No newline at end of file + } + +def notification_preferences_context(request): + """Add user notification preferences to all templates""" + try: + if 'userID' in request.session: + from .models import UserNotificationPreferences + from loginSystem.models import Administrator + user = Administrator.objects.get(pk=request.session['userID']) + try: + preferences = UserNotificationPreferences.objects.get(user=user) + return { + 'backup_notification_dismissed': preferences.backup_notification_dismissed, + 'ai_scanner_notification_dismissed': preferences.ai_scanner_notification_dismissed + } + except UserNotificationPreferences.DoesNotExist: + return { + 'backup_notification_dismissed': False, + 'ai_scanner_notification_dismissed': False + } + except: + pass + + return { + 'backup_notification_dismissed': False, + 'ai_scanner_notification_dismissed': False + } \ No newline at end of file diff --git a/baseTemplate/migrations/0002_usernotificationpreferences.py b/baseTemplate/migrations/0002_usernotificationpreferences.py new file mode 100644 index 000000000..3db48ac84 --- /dev/null +++ b/baseTemplate/migrations/0002_usernotificationpreferences.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2024-01-01 00:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('baseTemplate', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserNotificationPreferences', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('backup_notification_dismissed', models.BooleanField(default=False, help_text='Whether user has dismissed the backup notification')), + ('ai_scanner_notification_dismissed', models.BooleanField(default=False, help_text='Whether user has dismissed the AI scanner notification')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='notification_preferences', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'User Notification Preferences', + 'verbose_name_plural': 'User Notification Preferences', + }, + ), + ] diff --git a/baseTemplate/models.py b/baseTemplate/models.py index 3ced4ced5..b02b2ca38 100644 --- a/baseTemplate/models.py +++ b/baseTemplate/models.py @@ -2,6 +2,7 @@ from django.db import models +from django.contrib.auth.models import User # Create your models here. @@ -11,4 +12,19 @@ class version(models.Model): build = models.IntegerField() class CyberPanelCosmetic(models.Model): - MainDashboardCSS = models.TextField(default='') \ No newline at end of file + MainDashboardCSS = models.TextField(default='') + +class UserNotificationPreferences(models.Model): + """Model to store user notification dismissal preferences""" + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='notification_preferences') + backup_notification_dismissed = models.BooleanField(default=False, help_text="Whether user has dismissed the backup notification") + ai_scanner_notification_dismissed = models.BooleanField(default=False, help_text="Whether user has dismissed the AI scanner notification") + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "User Notification Preferences" + verbose_name_plural = "User Notification Preferences" + + def __str__(self): + return f"Notification Preferences for {self.user.username}" \ No newline at end of file diff --git a/baseTemplate/signals.py b/baseTemplate/signals.py new file mode 100644 index 000000000..6c7b22340 --- /dev/null +++ b/baseTemplate/signals.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.contrib.auth.models import User +from .models import UserNotificationPreferences + + +@receiver(post_save, sender=User) +def create_user_notification_preferences(sender, instance, created, **kwargs): + """Create default notification preferences when a new user is created""" + if created: + UserNotificationPreferences.objects.create( + user=instance, + backup_notification_dismissed=False, + ai_scanner_notification_dismissed=False + ) + + +@receiver(post_save, sender=User) +def save_user_notification_preferences(sender, instance, **kwargs): + """Save notification preferences when user is saved""" + if hasattr(instance, 'notification_preferences'): + instance.notification_preferences.save() diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 3a6986ddd..742a8f49f 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -1841,10 +1841,10 @@ // Backup notification banner logic function checkBackupStatus() { - // Check if user has dismissed the notification in this session - if (sessionStorage.getItem('backupNotificationDismissed') === 'true') { - return; - } + // Check if user has dismissed the notification permanently (from server-side context) + {% if backup_notification_dismissed %} + return; // Notification already dismissed permanently + {% endif %} // Check if user has backup configured (you'll need to implement this API) // For now, we'll show it by default unless they have a backup plan @@ -1871,16 +1871,34 @@ const body = document.body; banner.classList.remove('show'); body.classList.remove('notification-shown'); - // Remember dismissal for this session - sessionStorage.setItem('backupNotificationDismissed', 'true'); + + // Dismiss permanently via API + fetch('/base/dismiss_backup_notification', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCookie('csrftoken') + } + }) + .then(response => response.json()) + .then(data => { + if (data.status === 1) { + console.log('Backup notification dismissed permanently'); + } else { + console.error('Failed to dismiss backup notification:', data.error); + } + }) + .catch(error => { + console.error('Error dismissing backup notification:', error); + }); } // AI Scanner Notification Functions function checkAIScannerStatus() { - // Check if user has dismissed the notification in this session - if (sessionStorage.getItem('aiScannerNotificationDismissed') === 'true') { - return; - } + // Check if user has dismissed the notification permanently (from server-side context) + {% if ai_scanner_notification_dismissed %} + return; // Notification already dismissed permanently + {% endif %} // Check if we're not already on the AI Scanner page if (!window.location.href.includes('aiscanner')) { @@ -1900,8 +1918,26 @@ const body = document.body; banner.classList.remove('show'); body.classList.remove('ai-scanner-shown'); - // Remember dismissal for this session - sessionStorage.setItem('aiScannerNotificationDismissed', 'true'); + + // Dismiss permanently via API + fetch('/base/dismiss_ai_scanner_notification', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCookie('csrftoken') + } + }) + .then(response => response.json()) + .then(data => { + if (data.status === 1) { + console.log('AI scanner notification dismissed permanently'); + } else { + console.error('Failed to dismiss AI scanner notification:', data.error); + } + }) + .catch(error => { + console.error('Error dismissing AI scanner notification:', error); + }); } // Check both notification statuses when page loads diff --git a/baseTemplate/urls.py b/baseTemplate/urls.py index 6ebdc293b..c6190ea66 100644 --- a/baseTemplate/urls.py +++ b/baseTemplate/urls.py @@ -24,4 +24,7 @@ urlpatterns = [ re_path(r'^getSSHUserActivity$', views.getSSHUserActivity, name='getSSHUserActivity'), re_path(r'^getTopProcesses$', views.getTopProcesses, name='getTopProcesses'), re_path(r'^analyzeSSHSecurity$', views.analyzeSSHSecurity, name='analyzeSSHSecurity'), + re_path(r'^dismiss_backup_notification$', views.dismiss_backup_notification, name='dismiss_backup_notification'), + re_path(r'^dismiss_ai_scanner_notification$', views.dismiss_ai_scanner_notification, name='dismiss_ai_scanner_notification'), + re_path(r'^get_notification_preferences$', views.get_notification_preferences, name='get_notification_preferences'), ] diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 4e4bfef57..22adee65d 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -6,7 +6,7 @@ from django.http import HttpResponse from plogical.getSystemInformation import SystemInformation import json from loginSystem.views import loadLoginPage -from .models import version +from .models import version, UserNotificationPreferences import requests import subprocess import shlex @@ -1305,3 +1305,88 @@ def getTopProcesses(request): except Exception as e: return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500) + +@csrf_exempt +@require_POST +def dismiss_backup_notification(request): + """API endpoint to permanently dismiss the backup notification for the current user""" + try: + user_id = request.session.get('userID') + if not user_id: + return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403) + + # Get or create user notification preferences + user = Administrator.objects.get(pk=user_id) + preferences, created = UserNotificationPreferences.objects.get_or_create( + user=user, + defaults={ + 'backup_notification_dismissed': False, + 'ai_scanner_notification_dismissed': False + } + ) + + # Mark backup notification as dismissed + preferences.backup_notification_dismissed = True + preferences.save() + + return HttpResponse(json.dumps({'status': 1, 'message': 'Backup notification dismissed permanently'}), content_type='application/json') + + except Exception as e: + return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500) + +@csrf_exempt +@require_POST +def dismiss_ai_scanner_notification(request): + """API endpoint to permanently dismiss the AI scanner notification for the current user""" + try: + user_id = request.session.get('userID') + if not user_id: + return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403) + + # Get or create user notification preferences + user = Administrator.objects.get(pk=user_id) + preferences, created = UserNotificationPreferences.objects.get_or_create( + user=user, + defaults={ + 'backup_notification_dismissed': False, + 'ai_scanner_notification_dismissed': False + } + ) + + # Mark AI scanner notification as dismissed + preferences.ai_scanner_notification_dismissed = True + preferences.save() + + return HttpResponse(json.dumps({'status': 1, 'message': 'AI scanner notification dismissed permanently'}), content_type='application/json') + + except Exception as e: + return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500) + +@csrf_exempt +@require_GET +def get_notification_preferences(request): + """API endpoint to get current user's notification preferences""" + try: + user_id = request.session.get('userID') + if not user_id: + return HttpResponse(json.dumps({'status': 0, 'error': 'Not logged in'}), content_type='application/json', status=403) + + # Get user notification preferences + user = Administrator.objects.get(pk=user_id) + try: + preferences = UserNotificationPreferences.objects.get(user=user) + return HttpResponse(json.dumps({ + 'status': 1, + 'backup_notification_dismissed': preferences.backup_notification_dismissed, + 'ai_scanner_notification_dismissed': preferences.ai_scanner_notification_dismissed + }), content_type='application/json') + except UserNotificationPreferences.DoesNotExist: + # Return default values if preferences don't exist yet + return HttpResponse(json.dumps({ + 'status': 1, + 'backup_notification_dismissed': False, + 'ai_scanner_notification_dismissed': False + }), content_type='application/json') + + except Exception as e: + return HttpResponse(json.dumps({'status': 0, 'error': str(e)}), content_type='application/json', status=500) diff --git a/guides/CUSTOM_CSS_GUIDE.md b/guides/CUSTOM_CSS_GUIDE.md new file mode 100644 index 000000000..cc5d6f969 --- /dev/null +++ b/guides/CUSTOM_CSS_GUIDE.md @@ -0,0 +1,829 @@ +# CyberPanel 2.5.5-dev Custom CSS Guide + +A comprehensive guide for creating custom CSS that fully works with the new design system of CyberPanel 2.5.5-dev. + +## Table of Contents + +1. [Overview](#overview) +2. [Design System Architecture](#design-system-architecture) +3. [CSS Variables Reference](#css-variables-reference) +4. [Component Structure](#component-structure) +5. [Customization Examples](#customization-examples) +6. [Best Practices](#best-practices) +7. [Troubleshooting](#troubleshooting) +8. [Advanced Techniques](#advanced-techniques) + +## Overview + +CyberPanel 2.5.5-dev features a modern, CSS-variable-based design system that supports both light and dark themes. The system is built with: + +- **CSS Custom Properties (Variables)** for consistent theming +- **Modern CSS Grid and Flexbox** layouts +- **Responsive design** principles +- **Dark mode support** with automatic theme switching +- **Component-based architecture** for easy customization + +## Design System Architecture + +### Core Structure + +The design system is built around CSS custom properties defined in `:root` and `[data-theme="dark"]` selectors: + +```css +:root { + /* Light Theme Variables */ + --bg-primary: #f0f0ff; + --bg-secondary: white; + --text-primary: #2f3640; + --accent-color: #5856d6; + /* ... more variables */ +} + +[data-theme="dark"] { + /* Dark Theme Variables */ + --bg-primary: #0f0f23; + --bg-secondary: #1a1a3e; + --text-primary: #e4e4e7; + --accent-color: #7c7ff3; + /* ... more variables */ +} +``` + +### Key Components + +1. **Header** (`#header`) - Top navigation bar +2. **Sidebar** (`#sidebar`) - Left navigation panel +3. **Main Content** (`#main-content`) - Page content area +4. **Cards** (`.content-card`) - Content containers +5. **Buttons** (`.btn`) - Interactive elements +6. **Forms** (`.form-*`) - Input elements + +## CSS Variables Reference + +### Color Variables + +#### Background Colors +```css +--bg-primary /* Main background color */ +--bg-secondary /* Card/container background */ +--bg-sidebar /* Sidebar background */ +--bg-sidebar-item /* Sidebar menu item background */ +--bg-hover /* Hover state background */ +``` + +#### Text Colors +```css +--text-primary /* Main text color */ +--text-secondary /* Secondary text color */ +--text-heading /* Heading text color */ +``` + +#### Accent Colors +```css +--accent-color /* Primary accent color */ +--accent-hover /* Accent hover state */ +--danger-color /* Error/danger color */ +--success-color /* Success color */ +``` + +#### Border & Shadow +```css +--border-color /* Default border color */ +--shadow-color /* Default shadow color */ +``` + +### Special Variables + +#### Gradients +```css +--warning-bg /* Warning banner gradient */ +--ai-banner-bg /* AI scanner banner gradient */ +``` + +#### Status Colors +```css +--success-bg /* Success background */ +--success-border /* Success border */ +--danger-bg /* Danger background */ +--danger-border /* Danger border */ +--warning-bg /* Warning background */ +--info-bg /* Info background */ +``` + +## Component Structure + +### Header Component + +```css +#header { + background: var(--bg-secondary); + height: 80px; + display: flex; + align-items: center; + padding: 0 30px; + box-shadow: 0 2px 12px var(--shadow-color); + position: fixed; + top: 0; + left: 260px; + right: 0; + z-index: 1000; +} +``` + +**Customization Example:** +```css +/* Change header height and add custom styling */ +#header { + height: 100px; + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + border-bottom: 3px solid var(--accent-color); +} + +#header .logo-text .brand { + font-size: 32px; + text-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +``` + +### Sidebar Component + +```css +#sidebar { + width: 260px; + background: var(--bg-sidebar); + height: 100vh; + position: fixed; + left: 0; + top: 0; + overflow-y: auto; + z-index: 1001; +} +``` + +**Customization Example:** +```css +/* Make sidebar wider with custom styling */ +#sidebar { + width: 300px; + background: linear-gradient(180deg, var(--bg-sidebar), var(--bg-secondary)); + border-right: 2px solid var(--accent-color); +} + +#sidebar .menu-item { + margin: 4px 20px; + border-radius: 12px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#sidebar .menu-item:hover { + transform: translateX(5px); + box-shadow: 0 4px 12px var(--shadow-color); +} +``` + +### Content Cards + +```css +.content-card { + background: var(--bg-secondary); + border-radius: 12px; + padding: 30px; + box-shadow: 0 2px 8px var(--shadow-color); + border: 1px solid var(--border-color); + margin-bottom: 25px; +} +``` + +**Customization Example:** +```css +/* Add glassmorphism effect to cards */ +.content-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +[data-theme="dark"] .content-card { + background: rgba(26, 26, 62, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); +} +``` + +## Customization Examples + +### 1. Complete Theme Override + +```css +/* Custom Purple Theme */ +:root { + --bg-primary: #f8f4ff; + --bg-secondary: #ffffff; + --bg-sidebar: #f3f0ff; + --bg-hover: #e8e0ff; + --text-primary: #2d1b69; + --text-secondary: #6b46c1; + --accent-color: #8b5cf6; + --accent-hover: #7c3aed; + --border-color: #e0d7ff; + --shadow-color: rgba(139, 92, 246, 0.1); +} + +[data-theme="dark"] { + --bg-primary: #1a0b2e; + --bg-secondary: #2d1b69; + --bg-sidebar: #1e0a3e; + --bg-hover: #3d2a7a; + --text-primary: #f3f0ff; + --text-secondary: #c4b5fd; + --accent-color: #a78bfa; + --accent-hover: #8b5cf6; + --border-color: #4c1d95; + --shadow-color: rgba(139, 92, 246, 0.3); +} +``` + +### 2. Custom Button Styles + +```css +/* Custom button variants */ +.btn-custom { + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + border: none; + border-radius: 20px; + padding: 12px 24px; + color: white; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 4px 15px rgba(139, 92, 246, 0.3); + transition: all 0.3s ease; +} + +.btn-custom:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(139, 92, 246, 0.4); +} + +.btn-custom:active { + transform: translateY(0); +} +``` + +### 3. Custom Sidebar Menu Items + +```css +/* Animated sidebar menu items */ +#sidebar .menu-item { + position: relative; + overflow: hidden; +} + +#sidebar .menu-item::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s; +} + +#sidebar .menu-item:hover::before { + left: 100%; +} + +#sidebar .menu-item .icon-wrapper { + position: relative; + z-index: 1; +} +``` + +### 4. Custom Form Styling + +```css +/* Modern form inputs */ +.form-control { + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 12px 16px; + font-size: 14px; + transition: all 0.3s ease; + background: var(--bg-secondary); +} + +.form-control:focus { + border-color: var(--accent-color); + box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.1); + transform: translateY(-1px); +} + +.form-control::placeholder { + color: var(--text-secondary); + opacity: 0.7; +} +``` + +### 5. Custom Notifications + +```css +/* Custom notification banners */ +.notification-banner { + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + border-radius: 16px; + padding: 20px; + margin: 20px; + box-shadow: 0 8px 32px rgba(139, 92, 246, 0.3); + position: relative; + overflow: hidden; +} + +.notification-banner::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4); + animation: rainbow 3s linear infinite; +} + +@keyframes rainbow { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} +``` + +## Best Practices + +### 1. Use CSS Variables + +Always use CSS variables instead of hardcoded values: + +```css +/* ✅ Good */ +.custom-element { + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +/* ❌ Bad */ +.custom-element { + background: white; + color: #2f3640; + border: 1px solid #e8e9ff; +} +``` + +### 2. Support Both Themes + +Always provide both light and dark theme support: + +```css +.custom-element { + background: var(--bg-secondary); + color: var(--text-primary); +} + +/* Dark theme specific adjustments */ +[data-theme="dark"] .custom-element { + /* Additional dark theme styling if needed */ +} +``` + +### 3. Use Semantic Class Names + +```css +/* ✅ Good */ +.primary-button { } +.content-container { } +.navigation-item { } + +/* ❌ Bad */ +.red-button { } +.big-box { } +.item1 { } +``` + +### 4. Maintain Responsive Design + +```css +.custom-element { + padding: 20px; + font-size: 16px; +} + +@media (max-width: 768px) { + .custom-element { + padding: 15px; + font-size: 14px; + } +} +``` + +### 5. Use Modern CSS Features + +```css +.custom-element { + /* Use CSS Grid for layouts */ + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + + /* Use Flexbox for alignment */ + align-items: center; + justify-content: space-between; + + /* Use CSS custom properties for calculations */ + --element-height: 60px; + height: var(--element-height); + + /* Use modern selectors */ + &:hover { } + &:focus-within { } +} +``` + +## Troubleshooting + +### Common Issues + +#### 1. Custom CSS Not Applying + +**Problem:** Custom CSS doesn't appear to be working. + +**Solution:** +- Check CSS specificity - use more specific selectors +- Ensure CSS is placed after the base styles +- Use `!important` sparingly and only when necessary + +```css +/* Increase specificity */ +#main-content .content-card .custom-element { + background: var(--bg-secondary); +} +``` + +#### 2. Dark Mode Not Working + +**Problem:** Custom styles don't adapt to dark mode. + +**Solution:** +- Always use CSS variables +- Test both light and dark themes +- Provide dark mode specific overrides when needed + +```css +.custom-element { + background: var(--bg-secondary); + color: var(--text-primary); +} + +[data-theme="dark"] .custom-element { + /* Dark mode specific adjustments */ + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +``` + +#### 3. Responsive Issues + +**Problem:** Custom styles break on mobile devices. + +**Solution:** +- Use relative units (rem, em, %) +- Test on different screen sizes +- Use CSS Grid and Flexbox for responsive layouts + +```css +.custom-element { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 1rem; +} + +@media (max-width: 768px) { + .custom-element { + padding: 0.5rem; + } +} +``` + +## Advanced Techniques + +### 1. CSS Animations + +```css +/* Smooth page transitions */ +.page-transition { + animation: fadeIn 0.3s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Hover animations */ +.interactive-element { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.interactive-element:hover { + transform: translateY(-2px) scale(1.02); + box-shadow: 0 8px 25px var(--shadow-color); +} +``` + +### 2. CSS Grid Layouts + +```css +/* Advanced grid layouts */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + padding: 20px; +} + +.dashboard-card { + grid-column: span 1; + background: var(--bg-secondary); + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px var(--shadow-color); +} + +.dashboard-card.featured { + grid-column: span 2; +} + +@media (max-width: 768px) { + .dashboard-card.featured { + grid-column: span 1; + } +} +``` + +### 3. CSS Custom Properties with JavaScript + +```css +/* Dynamic theming with CSS variables */ +:root { + --custom-accent: #5856d6; + --custom-accent-hover: #4644c0; +} + +.custom-theme { + --accent-color: var(--custom-accent); + --accent-hover: var(--custom-accent-hover); +} +``` + +### 4. Advanced Selectors + +```css +/* Complex selectors for specific styling */ +#sidebar .menu-item:not(.active):hover { + background: var(--bg-hover); + transform: translateX(5px); +} + +.content-card > *:first-child { + margin-top: 0; +} + +.content-card > *:last-child { + margin-bottom: 0; +} + +/* Attribute selectors */ +[data-status="success"] { + border-left: 4px solid var(--success-color); +} + +[data-status="error"] { + border-left: 4px solid var(--danger-color); +} +``` + +### 5. CSS Functions and Calculations + +```css +/* Using CSS functions */ +.responsive-text { + font-size: clamp(14px, 2.5vw, 18px); + line-height: calc(1.5em + 0.5vw); +} + +.dynamic-spacing { + padding: calc(1rem + 2vw); + margin: calc(0.5rem + 1vw); +} + +/* CSS custom properties with calculations */ +:root { + --base-size: 16px; + --scale-factor: 1.2; + --large-size: calc(var(--base-size) * var(--scale-factor)); +} +``` + +## Implementation Guide + +### Step 1: Access the Design Page + +1. Log into CyberPanel +2. Navigate to **Design** in the sidebar +3. Scroll down to the **Custom CSS** section + +### Step 2: Add Your Custom CSS + +1. Paste your custom CSS into the textarea +2. Click **Save Changes** +3. Refresh the page to see your changes + +### Step 3: Test Your Changes + +1. Test in both light and dark modes +2. Test on different screen sizes +3. Verify all interactive elements work correctly + +### Step 4: Iterate and Refine + +1. Make adjustments as needed +2. Test thoroughly before finalizing +3. Document your customizations + +## Example: Complete Custom Theme + +Here's a complete example of a custom theme that you can use as a starting point: + +```css +/* Custom CyberPanel Theme - Ocean Blue */ + +/* Light Theme */ +:root { + --bg-primary: #f0f9ff; + --bg-secondary: #ffffff; + --bg-sidebar: #e0f2fe; + --bg-sidebar-item: #ffffff; + --bg-hover: #bae6fd; + --text-primary: #0c4a6e; + --text-secondary: #0369a1; + --text-heading: #0c4a6e; + --border-color: #bae6fd; + --shadow-color: rgba(6, 105, 161, 0.1); + --accent-color: #0284c7; + --accent-hover: #0369a1; + --danger-color: #dc2626; + --success-color: #059669; + --warning-bg: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + --ai-banner-bg: linear-gradient(135deg, #0284c7 0%, #0369a1 50%, #0c4a6e 100%); +} + +/* Dark Theme */ +[data-theme="dark"] { + --bg-primary: #0c4a6e; + --bg-secondary: #075985; + --bg-sidebar: #0c4a6e; + --bg-sidebar-item: #075985; + --bg-hover: #0369a1; + --text-primary: #e0f2fe; + --text-secondary: #bae6fd; + --text-heading: #f0f9ff; + --border-color: #0369a1; + --shadow-color: rgba(0, 0, 0, 0.3); + --accent-color: #38bdf8; + --accent-hover: #0ea5e9; + --danger-color: #f87171; + --success-color: #34d399; + --warning-bg: linear-gradient(135deg, #78350f 0%, #92400e 100%); + --ai-banner-bg: linear-gradient(135deg, #0c4a6e 0%, #075985 50%, #0369a1 100%); +} + +/* Custom Header Styling */ +#header { + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + box-shadow: 0 4px 20px var(--shadow-color); +} + +#header .logo-text .brand { + color: white; + text-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +/* Custom Sidebar Styling */ +#sidebar { + background: linear-gradient(180deg, var(--bg-sidebar), var(--bg-secondary)); + border-right: 3px solid var(--accent-color); +} + +#sidebar .menu-item { + border-radius: 12px; + margin: 4px 20px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +#sidebar .menu-item:hover { + transform: translateX(8px); + box-shadow: 0 4px 15px var(--shadow-color); +} + +#sidebar .menu-item.active { + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + box-shadow: 0 4px 15px rgba(2, 132, 199, 0.3); +} + +/* Custom Content Cards */ +.content-card { + background: var(--bg-secondary); + border-radius: 16px; + box-shadow: 0 4px 20px var(--shadow-color); + border: 1px solid var(--border-color); + position: relative; + overflow: hidden; +} + +.content-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--accent-color), var(--accent-hover)); +} + +/* Custom Buttons */ +.btn { + border-radius: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.btn-primary { + background: linear-gradient(135deg, var(--accent-color), var(--accent-hover)); + box-shadow: 0 4px 15px rgba(2, 132, 199, 0.3); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(2, 132, 199, 0.4); +} + +/* Custom Form Elements */ +.form-control { + border: 2px solid var(--border-color); + border-radius: 12px; + transition: all 0.3s ease; +} + +.form-control:focus { + border-color: var(--accent-color); + box-shadow: 0 0 0 4px rgba(2, 132, 199, 0.1); + transform: translateY(-1px); +} + +/* Custom Animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.content-card { + animation: slideIn 0.5s ease-out; +} + +/* Responsive Design */ +@media (max-width: 768px) { + #sidebar { + width: 100%; + height: auto; + position: relative; + } + + #header { + left: 0; + } + + .content-card { + margin: 10px; + padding: 20px; + } +} +``` + +This guide provides everything you need to create beautiful, functional custom CSS for CyberPanel 2.5.5+. Remember to always test your changes thoroughly and use the CSS variables for consistency across themes. diff --git a/guides/INDEX.md b/guides/INDEX.md index 6df7c3900..d31a90f61 100644 --- a/guides/INDEX.md +++ b/guides/INDEX.md @@ -14,6 +14,9 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu ### 📧 Email & Marketing - **[Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)** - Step-by-step guide for installing and configuring Mautic email marketing platform +### 🎨 Customization & Design +- **[Custom CSS Guide](CUSTOM_CSS_GUIDE.md)** - Complete guide for creating custom CSS that works with CyberPanel 2.5.5-dev design system + ### 📖 General Documentation - **[README](../README.md)** - Main CyberPanel documentation with installation instructions and feature overview - **[Contributing Guide](CONTRIBUTING.md)** - Guidelines for contributing to the CyberPanel project @@ -23,7 +26,8 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu 1. **New to CyberPanel?** Start with the [README](../README.md) for installation and basic setup 2. **Need Docker help?** Check the [Docker Command Execution Guide](Docker_Command_Execution_Guide.md) 3. **Setting up email marketing?** Follow the [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md) -4. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md) +4. **Want to customize the interface?** Check the [Custom CSS Guide](CUSTOM_CSS_GUIDE.md) +5. **Want to contribute?** Read the [Contributing Guide](CONTRIBUTING.md) ## 🔍 Finding What You Need @@ -31,6 +35,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu - **Docker Features**: [Docker Command Execution Guide](Docker_Command_Execution_Guide.md) - **Security Features**: [AI Scanner Documentation](AIScannerDocs.md) - **Email Marketing**: [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md) +- **Customization & Design**: [Custom CSS Guide](CUSTOM_CSS_GUIDE.md) - **Development**: [Contributing Guide](CONTRIBUTING.md) ## 📝 Guide Categories @@ -45,6 +50,11 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu - Third-party applications - Custom configurations +### 🎨 **Customization** +- Custom CSS theming +- Design system integration +- Interface personalization + ### 📖 **Documentation** - Installation guides - Configuration tutorials diff --git a/run_migration.py b/run_migration.py new file mode 100644 index 000000000..913a3473f --- /dev/null +++ b/run_migration.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Migration script for UserNotificationPreferences model +Run this script to apply the database migration for notification preferences +""" + +import os +import sys +import django + +# Add the project directory to Python path +sys.path.append('/usr/local/CyberCP') + +# Set up Django environment +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CyberCP.settings') +django.setup() + +from django.core.management import execute_from_command_line + +if __name__ == '__main__': + print("Running migration for UserNotificationPreferences...") + try: + execute_from_command_line(['manage.py', 'migrate', 'baseTemplate']) + print("Migration completed successfully!") + except Exception as e: + print(f"Migration failed: {e}") + sys.exit(1)