mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-10-26 00:36:34 +02:00
Add user notification preferences and related API endpoints
- Introduced a new model `UserNotificationPreferences` to store user-specific notification dismissal settings. - Added context processor to include notification preferences in templates. - Implemented API endpoints to dismiss backup and AI scanner notifications permanently. - Updated front-end logic to handle notification dismissal via server-side checks and API calls. - Enhanced documentation to include a new Custom CSS Guide for theme customization.
This commit is contained in:
@@ -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',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
23
README.md
23
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/`
|
||||
|
||||
@@ -6,3 +6,6 @@ from django.apps import AppConfig
|
||||
|
||||
class BasetemplateConfig(AppConfig):
|
||||
name = 'baseTemplate'
|
||||
|
||||
def ready(self):
|
||||
import baseTemplate.signals
|
||||
@@ -23,4 +23,30 @@ def cosmetic_context(request):
|
||||
cosmetic.save()
|
||||
return {
|
||||
'cosmetic': cosmetic
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
32
baseTemplate/migrations/0002_usernotificationpreferences.py
Normal file
32
baseTemplate/migrations/0002_usernotificationpreferences.py
Normal file
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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='')
|
||||
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}"
|
||||
23
baseTemplate/signals.py
Normal file
23
baseTemplate/signals.py
Normal file
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
829
guides/CUSTOM_CSS_GUIDE.md
Normal file
829
guides/CUSTOM_CSS_GUIDE.md
Normal file
@@ -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.
|
||||
@@ -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
|
||||
|
||||
28
run_migration.py
Normal file
28
run_migration.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user