diff --git a/testPlugin/__init__.py b/testPlugin/__init__.py new file mode 100644 index 000000000..695a722b6 --- /dev/null +++ b/testPlugin/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +default_app_config = 'testPlugin.apps.TestPluginConfig' diff --git a/testPlugin/admin.py b/testPlugin/admin.py new file mode 100644 index 000000000..cd858aeea --- /dev/null +++ b/testPlugin/admin.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from .models import TestPluginSettings, TestPluginLog + + +@admin.register(TestPluginSettings) +class TestPluginSettingsAdmin(admin.ModelAdmin): + list_display = ['user', 'plugin_enabled', 'test_count', 'last_test_time'] + list_filter = ['plugin_enabled', 'last_test_time'] + search_fields = ['user__username', 'custom_message'] + readonly_fields = ['last_test_time'] + + +@admin.register(TestPluginLog) +class TestPluginLogAdmin(admin.ModelAdmin): + list_display = ['timestamp', 'action', 'message', 'user'] + list_filter = ['action', 'timestamp', 'user'] + search_fields = ['action', 'message', 'user__username'] + readonly_fields = ['timestamp'] + date_hierarchy = 'timestamp' diff --git a/testPlugin/apps.py b/testPlugin/apps.py new file mode 100644 index 000000000..ae29de970 --- /dev/null +++ b/testPlugin/apps.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from django.apps import AppConfig + + +class TestPluginConfig(AppConfig): + name = 'testPlugin' + verbose_name = 'Test Plugin' + + def ready(self): + # Import signal handlers + import testPlugin.signals diff --git a/testPlugin/install.sh b/testPlugin/install.sh new file mode 100644 index 000000000..880263cad --- /dev/null +++ b/testPlugin/install.sh @@ -0,0 +1,305 @@ +#!/bin/bash + +# Test Plugin Installation Script for CyberPanel +# This script installs the test plugin from GitHub + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +PLUGIN_NAME="testPlugin" +PLUGIN_DIR="/home/cyberpanel/plugins" +CYBERPANEL_DIR="/usr/local/CyberCP" +GITHUB_REPO="https://github.com/cyberpanel/testPlugin.git" +TEMP_DIR="/tmp/cyberpanel_plugin_install" + +# Function to print colored output +print_status() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Function to check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + print_error "This script must be run as root" + exit 1 + fi +} + +# Function to check if CyberPanel is installed +check_cyberpanel() { + if [ ! -d "$CYBERPANEL_DIR" ]; then + print_error "CyberPanel is not installed at $CYBERPANEL_DIR" + exit 1 + fi + print_success "CyberPanel installation found" +} + +# Function to create necessary directories +create_directories() { + print_status "Creating plugin directories..." + + # Create plugins directory if it doesn't exist + mkdir -p "$PLUGIN_DIR" + chown -R cyberpanel:cyberpanel "$PLUGIN_DIR" + chmod 755 "$PLUGIN_DIR" + + # Create temp directory + mkdir -p "$TEMP_DIR" + + print_success "Directories created" +} + +# Function to download plugin from GitHub +download_plugin() { + print_status "Downloading plugin from GitHub..." + + # Remove existing temp directory + rm -rf "$TEMP_DIR" + + # Clone the repository + if command -v git &> /dev/null; then + git clone "$GITHUB_REPO" "$TEMP_DIR" + else + print_error "Git is not installed. Please install git first." + exit 1 + fi + + print_success "Plugin downloaded" +} + +# Function to install plugin files +install_plugin() { + print_status "Installing plugin files..." + + # Copy plugin files to CyberPanel directory + cp -r "$TEMP_DIR" "$CYBERPANEL_DIR/$PLUGIN_NAME" + + # Set proper permissions + chown -R cyberpanel:cyberpanel "$CYBERPANEL_DIR/$PLUGIN_NAME" + chmod -R 755 "$CYBERPANEL_DIR/$PLUGIN_NAME" + + # Create symlink in plugins directory + ln -sf "$CYBERPANEL_DIR/$PLUGIN_NAME" "$PLUGIN_DIR/$PLUGIN_NAME" + + print_success "Plugin files installed" +} + +# Function to update Django settings +update_django_settings() { + print_status "Updating Django settings..." + + SETTINGS_FILE="$CYBERPANEL_DIR/cyberpanel/settings.py" + + if [ -f "$SETTINGS_FILE" ]; then + # Check if plugin is already in INSTALLED_APPS + if ! grep -q "testPlugin" "$SETTINGS_FILE"; then + # Add plugin to INSTALLED_APPS + sed -i '/^INSTALLED_APPS = \[/a\ "testPlugin",' "$SETTINGS_FILE" + print_success "Added testPlugin to INSTALLED_APPS" + else + print_warning "testPlugin already in INSTALLED_APPS" + fi + else + print_error "Django settings file not found at $SETTINGS_FILE" + exit 1 + fi +} + +# Function to update URL configuration +update_urls() { + print_status "Updating URL configuration..." + + URLS_FILE="$CYBERPANEL_DIR/cyberpanel/urls.py" + + if [ -f "$URLS_FILE" ]; then + # Check if plugin URLs are already included + if ! grep -q "testPlugin.urls" "$URLS_FILE"; then + # Add plugin URLs + sed -i '/^urlpatterns = \[/a\ path("testPlugin/", include("testPlugin.urls")),' "$URLS_FILE" + print_success "Added testPlugin URLs" + else + print_warning "testPlugin URLs already configured" + fi + else + print_error "URLs file not found at $URLS_FILE" + exit 1 + fi +} + +# Function to run Django migrations +run_migrations() { + print_status "Running Django migrations..." + + cd "$CYBERPANEL_DIR" + + # Run migrations + python3 manage.py makemigrations testPlugin + python3 manage.py migrate testPlugin + + print_success "Migrations completed" +} + +# Function to collect static files +collect_static() { + print_status "Collecting static files..." + + cd "$CYBERPANEL_DIR" + python3 manage.py collectstatic --noinput + + print_success "Static files collected" +} + +# Function to restart CyberPanel services +restart_services() { + print_status "Restarting CyberPanel services..." + + # Restart LiteSpeed + systemctl restart lscpd + + # Restart CyberPanel + systemctl restart cyberpanel + + print_success "Services restarted" +} + +# Function to verify installation +verify_installation() { + print_status "Verifying installation..." + + # Check if plugin files exist + if [ -d "$CYBERPANEL_DIR/$PLUGIN_NAME" ]; then + print_success "Plugin directory exists" + else + print_error "Plugin directory not found" + exit 1 + fi + + # Check if symlink exists + if [ -L "$PLUGIN_DIR/$PLUGIN_NAME" ]; then + print_success "Plugin symlink created" + else + print_error "Plugin symlink not found" + exit 1 + fi + + # Check if meta.xml exists + if [ -f "$CYBERPANEL_DIR/$PLUGIN_NAME/meta.xml" ]; then + print_success "Plugin metadata found" + else + print_error "Plugin metadata not found" + exit 1 + fi + + print_success "Installation verified successfully" +} + +# Function to clean up +cleanup() { + print_status "Cleaning up temporary files..." + rm -rf "$TEMP_DIR" + print_success "Cleanup completed" +} + +# Function to show installation summary +show_summary() { + echo "" + echo "==========================================" + echo "Test Plugin Installation Summary" + echo "==========================================" + echo "Plugin Name: $PLUGIN_NAME" + echo "Installation Directory: $CYBERPANEL_DIR/$PLUGIN_NAME" + echo "Plugin Directory: $PLUGIN_DIR/$PLUGIN_NAME" + echo "Access URL: https://your-domain:8090/testPlugin/" + echo "" + echo "Features Installed:" + echo "✓ Enable/Disable Toggle" + echo "✓ Test Button with Popup Messages" + echo "✓ Settings Page" + echo "✓ Activity Logs" + echo "✓ Inline Integration" + echo "✓ Complete Documentation" + echo "✓ Official CyberPanel Guide" + echo "✓ Advanced Development Guide" + echo "" + echo "To uninstall, run: $0 --uninstall" + echo "==========================================" +} + +# Function to uninstall plugin +uninstall_plugin() { + print_status "Uninstalling testPlugin..." + + # Remove plugin files + rm -rf "$CYBERPANEL_DIR/$PLUGIN_NAME" + rm -f "$PLUGIN_DIR/$PLUGIN_NAME" + + # Remove from Django settings + SETTINGS_FILE="$CYBERPANEL_DIR/cyberpanel/settings.py" + if [ -f "$SETTINGS_FILE" ]; then + sed -i '/testPlugin/d' "$SETTINGS_FILE" + fi + + # Remove from URLs + URLS_FILE="$CYBERPANEL_DIR/cyberpanel/urls.py" + if [ -f "$URLS_FILE" ]; then + sed -i '/testPlugin/d' "$URLS_FILE" + fi + + # Restart services + restart_services + + print_success "Plugin uninstalled successfully" +} + +# Main installation function +main() { + echo "==========================================" + echo "CyberPanel Test Plugin Installer" + echo "==========================================" + echo "" + + # Check for uninstall flag + if [ "$1" = "--uninstall" ]; then + uninstall_plugin + exit 0 + fi + + # Run installation steps + check_root + check_cyberpanel + create_directories + download_plugin + install_plugin + update_django_settings + update_urls + run_migrations + collect_static + restart_services + verify_installation + cleanup + show_summary + + print_success "Test Plugin installation completed successfully!" +} + +# Run main function with all arguments +main "$@" diff --git a/testPlugin/meta.xml b/testPlugin/meta.xml new file mode 100644 index 000000000..5f6f1bae0 --- /dev/null +++ b/testPlugin/meta.xml @@ -0,0 +1,24 @@ + + + Test Plugin + Utility + A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration + 1.0.0 + CyberPanel Development Team + https://github.com/cyberpanel/testPlugin + MIT + + 3.6+ + 2.2+ + + + true + false + + + true + true + true + true + + diff --git a/testPlugin/models.py b/testPlugin/models.py new file mode 100644 index 000000000..146b361e7 --- /dev/null +++ b/testPlugin/models.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from django.db import models +from django.contrib.auth.models import User + + +class TestPluginSettings(models.Model): + """Model to store plugin settings and enable/disable state""" + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) + plugin_enabled = models.BooleanField(default=True, help_text="Enable or disable the plugin") + test_count = models.IntegerField(default=0, help_text="Number of times test button was clicked") + last_test_time = models.DateTimeField(auto_now=True, help_text="Last time test button was clicked") + custom_message = models.TextField(default="Test plugin is working!", help_text="Custom message for popup") + + class Meta: + verbose_name = "Test Plugin Settings" + verbose_name_plural = "Test Plugin Settings" + + def __str__(self): + return f"Test Plugin Settings - Enabled: {self.plugin_enabled}" + + +class TestPluginLog(models.Model): + """Model to store plugin activity logs""" + timestamp = models.DateTimeField(auto_now_add=True) + action = models.CharField(max_length=100) + message = models.TextField() + user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) + + class Meta: + verbose_name = "Test Plugin Log" + verbose_name_plural = "Test Plugin Logs" + ordering = ['-timestamp'] + + def __str__(self): + return f"{self.timestamp} - {self.action}: {self.message}" diff --git a/testPlugin/signals.py b/testPlugin/signals.py new file mode 100644 index 000000000..1a3d1cd6b --- /dev/null +++ b/testPlugin/signals.py @@ -0,0 +1,15 @@ +# -*- 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 TestPluginSettings + + +@receiver(post_save, sender=User) +def create_user_settings(sender, instance, created, **kwargs): + """Create default plugin settings when a new user is created""" + if created: + TestPluginSettings.objects.create( + user=instance, + plugin_enabled=True + ) diff --git a/testPlugin/static/testPlugin/css/testPlugin.css b/testPlugin/static/testPlugin/css/testPlugin.css new file mode 100644 index 000000000..8b68df38a --- /dev/null +++ b/testPlugin/static/testPlugin/css/testPlugin.css @@ -0,0 +1,336 @@ +/* Test Plugin CSS - Additional styles for better integration */ + +/* Popup Message Animations */ +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideOutRight { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(100%); + opacity: 0; + } +} + +/* Enhanced Button Styles */ +.btn-test { + position: relative; + overflow: hidden; +} + +.btn-test::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255, 255, 255, 0.3); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.btn-test:active::before { + width: 300px; + height: 300px; +} + +/* Toggle Switch Enhanced */ +.toggle-switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 34px; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + transition: .4s; + border-radius: 50%; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); +} + +input:checked + .slider { + background-color: #5856d6; +} + +input:checked + .slider:before { + transform: translateX(26px); +} + +/* Loading States */ +.loading { + opacity: 0.6; + pointer-events: none; +} + +.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + margin: -10px 0 0 -10px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Card Hover Effects */ +.plugin-card { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.plugin-card:hover { + transform: translateY(-8px); + box-shadow: 0 12px 32px rgba(0,0,0,0.15); +} + +/* Status Indicators */ +.status-indicator { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.status-indicator.enabled { + background: #e8f5e8; + color: #388e3c; +} + +.status-indicator.disabled { + background: #ffebee; + color: #d32f2f; +} + +/* Responsive Enhancements */ +@media (max-width: 768px) { + .control-row { + flex-direction: column; + align-items: stretch; + } + + .control-group { + justify-content: space-between; + margin-bottom: 15px; + } + + .stats-grid { + grid-template-columns: 1fr; + gap: 15px; + } + + .logs-table { + font-size: 12px; + } + + .logs-table th, + .logs-table td { + padding: 8px 4px; + } +} + +@media (max-width: 480px) { + .test-plugin-wrapper { + padding: 10px; + } + + .plugin-header, + .control-panel, + .settings-form, + .logs-content { + padding: 15px; + } + + .plugin-header h1 { + font-size: 24px; + flex-direction: column; + text-align: center; + } + + .btn-test, + .btn-secondary { + width: 100%; + justify-content: center; + margin-bottom: 10px; + } +} + +/* Dark Mode Support */ +@media (prefers-color-scheme: dark) { + :root { + --bg-primary: #1a1a1a; + --bg-secondary: #2d2d2d; + --text-primary: #ffffff; + --text-secondary: #b3b3b3; + --text-tertiary: #808080; + --border-primary: #404040; + --shadow-md: 0 2px 8px rgba(0,0,0,0.3); + --shadow-lg: 0 8px 24px rgba(0,0,0,0.4); + } +} + +/* Print Styles */ +@media print { + .test-plugin-wrapper { + background: white !important; + color: black !important; + } + + .btn-test, + .btn-secondary, + .toggle-switch { + display: none !important; + } + + .popup-message { + display: none !important; + } +} + +/* Additional styles for inline elements */ +.popup-message { + position: fixed; + top: 20px; + right: 20px; + background: white; + border-radius: 8px; + padding: 16px 20px; + box-shadow: 0 8px 24px rgba(0,0,0,0.15); + border-left: 4px solid #10b981; + z-index: 9999; + max-width: 400px; + transform: translateX(100%); + transition: transform 0.3s ease; +} + +.popup-message.show { + transform: translateX(0); +} + +.popup-message.error { + border-left-color: #ef4444; +} + +.popup-message.warning { + border-left-color: #f59e0b; +} + +.popup-title { + font-weight: 600; + color: var(--text-primary, #2f3640); + margin-bottom: 4px; +} + +.popup-content { + font-size: 14px; + color: var(--text-secondary, #64748b); + margin-bottom: 8px; +} + +.popup-time { + font-size: 12px; + color: var(--text-tertiary, #9ca3af); +} + +.popup-close { + position: absolute; + top: 8px; + right: 8px; + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: var(--text-tertiary, #9ca3af); +} + +.notification { + position: fixed; + top: 20px; + right: 20px; + background: white; + border-radius: 8px; + padding: 16px 20px; + box-shadow: 0 8px 24px rgba(0,0,0,0.15); + border-left: 4px solid #10b981; + z-index: 9999; + max-width: 400px; + transform: translateX(100%); + transition: transform 0.3s ease; +} + +.notification.show { + transform: translateX(0); +} + +.notification.error { + border-left-color: #ef4444; +} + +.notification-title { + font-weight: 600; + color: var(--text-primary, #2f3640); + margin-bottom: 4px; +} + +.notification-content { + font-size: 14px; + color: var(--text-secondary, #64748b); +} + +.popup-container { + position: fixed; + top: 0; + right: 0; + z-index: 9999; + pointer-events: none; +} diff --git a/testPlugin/static/testPlugin/js/testPlugin.js b/testPlugin/static/testPlugin/js/testPlugin.js new file mode 100644 index 000000000..988de5538 --- /dev/null +++ b/testPlugin/static/testPlugin/js/testPlugin.js @@ -0,0 +1,323 @@ +/** + * Test Plugin JavaScript + * Handles all client-side functionality for the test plugin + */ + +class TestPlugin { + constructor() { + this.init(); + } + + init() { + this.bindEvents(); + this.initializeComponents(); + } + + bindEvents() { + // Toggle switch functionality + const toggleSwitch = document.getElementById('plugin-toggle'); + if (toggleSwitch) { + toggleSwitch.addEventListener('change', (e) => this.handleToggle(e)); + } + + // Test button functionality + const testButton = document.getElementById('test-button'); + if (testButton) { + testButton.addEventListener('click', (e) => this.handleTestClick(e)); + } + + // Settings form + const settingsForm = document.getElementById('settings-form'); + if (settingsForm) { + settingsForm.addEventListener('submit', (e) => this.handleSettingsSubmit(e)); + } + + // Log filter + const actionFilter = document.getElementById('action-filter'); + if (actionFilter) { + actionFilter.addEventListener('change', (e) => this.handleLogFilter(e)); + } + } + + initializeComponents() { + // Initialize any components that need setup + this.initializeTooltips(); + this.initializeAnimations(); + } + + async handleToggle(event) { + const toggleSwitch = event.target; + const testButton = document.getElementById('test-button'); + + try { + const response = await fetch('/testPlugin/toggle/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.getCSRFToken() + } + }); + + const data = await response.json(); + + if (data.status === 1) { + if (testButton) { + testButton.disabled = !data.enabled; + } + this.showNotification('success', 'Plugin Toggle', data.message); + + // Update status indicator if exists + this.updateStatusIndicator(data.enabled); + + // Reload page after a short delay to update UI + setTimeout(() => { + window.location.reload(); + }, 1000); + } else { + this.showNotification('error', 'Error', data.error_message); + // Revert toggle state + toggleSwitch.checked = !toggleSwitch.checked; + } + } catch (error) { + this.showNotification('error', 'Error', 'Failed to toggle plugin'); + // Revert toggle state + toggleSwitch.checked = !toggleSwitch.checked; + } + } + + async handleTestClick(event) { + const testButton = event.target; + + if (testButton.disabled) return; + + // Add loading state + testButton.classList.add('loading'); + testButton.disabled = true; + const originalContent = testButton.innerHTML; + testButton.innerHTML = ' Testing...'; + + try { + const response = await fetch('/testPlugin/test/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.getCSRFToken() + } + }); + + const data = await response.json(); + + if (data.status === 1) { + // Update test count + const testCountElement = document.getElementById('test-count'); + if (testCountElement) { + testCountElement.textContent = data.test_count; + } + + // Show popup message + this.showPopup( + data.popup_message.type, + data.popup_message.title, + data.popup_message.message + ); + + // Add success animation + testButton.style.background = 'linear-gradient(135deg, #10b981, #059669)'; + setTimeout(() => { + testButton.style.background = ''; + }, 2000); + } else { + this.showNotification('error', 'Error', data.error_message); + } + } catch (error) { + this.showNotification('error', 'Error', 'Failed to execute test'); + } finally { + // Remove loading state + testButton.classList.remove('loading'); + testButton.disabled = false; + testButton.innerHTML = originalContent; + } + } + + async handleSettingsSubmit(event) { + event.preventDefault(); + + const form = event.target; + const formData = new FormData(form); + const data = { + custom_message: formData.get('custom_message') + }; + + try { + const response = await fetch('/testPlugin/update-settings/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': this.getCSRFToken() + }, + body: JSON.stringify(data) + }); + + const result = await response.json(); + + if (result.status === 1) { + this.showNotification('success', 'Settings Updated', result.message); + } else { + this.showNotification('error', 'Error', result.error_message); + } + } catch (error) { + this.showNotification('error', 'Error', 'Failed to update settings'); + } + } + + handleLogFilter(event) { + const selectedAction = event.target.value; + const logRows = document.querySelectorAll('.log-row'); + + logRows.forEach(row => { + if (selectedAction === '' || row.dataset.action === selectedAction) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + }); + } + + showPopup(type, title, message) { + const popupContainer = document.getElementById('popup-container') || this.createPopupContainer(); + const popup = document.createElement('div'); + popup.className = `popup-message ${type}`; + + popup.innerHTML = ` + + + + + `; + + popupContainer.appendChild(popup); + + // Show popup with animation + setTimeout(() => popup.classList.add('show'), 100); + + // Auto remove after 5 seconds + setTimeout(() => { + popup.classList.remove('show'); + setTimeout(() => popup.remove(), 300); + }, 5000); + } + + showNotification(type, title, message) { + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + + notification.innerHTML = ` +
${title}
+
${message}
+ `; + + document.body.appendChild(notification); + + // Show notification + setTimeout(() => notification.classList.add('show'), 100); + + // Auto remove after 3 seconds + setTimeout(() => { + notification.classList.remove('show'); + setTimeout(() => notification.remove(), 300); + }, 3000); + } + + createPopupContainer() { + const container = document.createElement('div'); + container.id = 'popup-container'; + container.className = 'popup-container'; + document.body.appendChild(container); + return container; + } + + updateStatusIndicator(enabled) { + const statusElements = document.querySelectorAll('.status-indicator'); + statusElements.forEach(element => { + element.className = `status-indicator ${enabled ? 'enabled' : 'disabled'}`; + element.innerHTML = enabled ? + ' Enabled' : + ' Disabled'; + }); + } + + initializeTooltips() { + // Add tooltips to buttons and controls + const elements = document.querySelectorAll('[data-tooltip]'); + elements.forEach(element => { + element.addEventListener('mouseenter', (e) => this.showTooltip(e)); + element.addEventListener('mouseleave', (e) => this.hideTooltip(e)); + }); + } + + showTooltip(event) { + const element = event.target; + const tooltipText = element.dataset.tooltip; + + if (!tooltipText) return; + + const tooltip = document.createElement('div'); + tooltip.className = 'tooltip'; + tooltip.textContent = tooltipText; + tooltip.style.cssText = ` + position: absolute; + background: #333; + color: white; + padding: 8px 12px; + border-radius: 4px; + font-size: 12px; + z-index: 10000; + pointer-events: none; + white-space: nowrap; + `; + + document.body.appendChild(tooltip); + + const rect = element.getBoundingClientRect(); + tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px'; + tooltip.style.top = rect.top - tooltip.offsetHeight - 8 + 'px'; + + element._tooltip = tooltip; + } + + hideTooltip(event) { + const element = event.target; + if (element._tooltip) { + element._tooltip.remove(); + delete element._tooltip; + } + } + + initializeAnimations() { + // Add entrance animations to cards + const cards = document.querySelectorAll('.plugin-card, .stat-card, .log-item'); + cards.forEach((card, index) => { + card.style.opacity = '0'; + card.style.transform = 'translateY(20px)'; + card.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; + + setTimeout(() => { + card.style.opacity = '1'; + card.style.transform = 'translateY(0)'; + }, index * 100); + }); + } + + getCSRFToken() { + const token = document.querySelector('[name=csrfmiddlewaretoken]'); + return token ? token.value : ''; + } +} + +// Initialize the plugin when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { + new TestPlugin(); +}); + +// Export for potential external use +window.TestPlugin = TestPlugin; diff --git a/testPlugin/templates/testPlugin/plugin_docs.html b/testPlugin/templates/testPlugin/plugin_docs.html new file mode 100644 index 000000000..365405396 --- /dev/null +++ b/testPlugin/templates/testPlugin/plugin_docs.html @@ -0,0 +1,1356 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Plugin Development Guide - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ +
+

+ + {% trans "Plugin Development Documentation" %} +

+

{% trans "Complete guide for developing, installing, and managing CyberPanel plugins" %}

+
+ + + + + +
+
+

Quick Installation Guide - CyberPanel Test Plugin

+ +
+
+

🚀 One-Command Installation

+

Install the test plugin with a single command using curl or wget.

+
+
+

📦 Manual Installation

+

Download and install manually with step-by-step instructions.

+
+
+

⚙️ Easy Management

+

Simple install/uninstall process with proper cleanup.

+
+
+ +

One-Command Installation

+
# Install the test plugin with a single command
+curl -sSL https://raw.githubusercontent.com/cyberpanel/testPlugin/main/install.sh | bash
+ +

Manual Installation

+
    +
  1. Download the plugin +
    git clone https://github.com/cyberpanel/testPlugin.git
    +cd testPlugin
    +
  2. +
  3. Run the installation script +
    chmod +x install.sh
    +./install.sh
    +
  4. +
  5. Access the plugin +
      +
    • URL: https://your-domain:8090/testPlugin/
    • +
    • Login with your CyberPanel admin credentials
    • +
    +
  6. +
+ +

Features Included

+
+
+

✅ Enable/Disable Toggle

+

Toggle the plugin on/off with a beautiful switch

+
+
+

✅ Test Button

+

Click to show popup messages from the side

+
+
+

✅ Settings Page

+

Configure custom messages and preferences

+
+
+

✅ Activity Logs

+

View all plugin activities with filtering

+
+
+

✅ Inline Integration

+

Loads within CyberPanel interface

+
+
+

✅ Responsive Design

+

Works perfectly on all devices

+
+
+ +

Uninstallation

+
# Uninstall the plugin
+./install.sh --uninstall
+ +

Troubleshooting

+

If you encounter any issues:

+
    +
  1. Check CyberPanel logs +
    tail -f /home/cyberpanel/logs/cyberpanel.log
    +
  2. +
  3. Restart CyberPanel services +
    systemctl restart lscpd
    +systemctl restart cyberpanel
    +
  4. +
  5. Verify installation +
    ls -la /home/cyberpanel/plugins/testPlugin
    +ls -la /usr/local/CyberCP/testPlugin
    +
  6. +
+ +
+ Note: This plugin is designed for testing and development purposes. Always backup your system before installing any plugins. +
+
+
+ + +
+
+

Getting Started with CyberPanel Plugin Development

+ +
+
+

🎯 Official Documentation

+

Based on the official CyberPanel plugin development guide from the CyberPanel team.

+
+
+

📚 Step-by-Step Tutorial

+

Complete walkthrough from development environment setup to plugin installation.

+
+
+

🔧 Signal Integration

+

Learn how to hook into CyberPanel events and respond to core functionality.

+
+
+ +
+ Source: This guide is based on the official CyberPanel documentation and the beautiful_names plugin repository. +
+ +

Prerequisites

+
    +
  • Python - Clear understanding of Python Programming Language
  • +
  • Django - Experience with Django framework
  • +
  • HTML (Basic) - Basic HTML knowledge
  • +
  • CSS (Basic) - Basic CSS knowledge
  • +
+ +

Note: You can use plain JavaScript in your plugins or any JavaScript framework. You just have to follow the norms of Django framework, because CyberPanel plugin is just another Django app.

+ +

Step 1: Set up your Development Environment

+ +

Clone CyberPanel Repository

+
git clone https://github.com/usmannasir/cyberpanel/ --single-branch v1.7.2-plugin
+ +

Create a Django App

+
cd v1.7.2-plugin
+django-admin startapp pluginName
+ +

Choose your plugin name wisely as it's of great importance. Once the Django app is created, you need to define a meta file for your plugin so that CyberPanel can read information about your plugin.

+ +

Create Meta File

+
cd pluginName
+nano meta.xml
+ +

Paste the following content in the meta.xml file:

+
<?xml version="1.0" encoding="UTF-8"?>
+<cyberpanelPluginConfig>
+  <name>customplugin</name>
+  <type>plugin</type>
+  <description>Plugin to make custom changes</description>
+  <version>0</version>
+</cyberpanelPluginConfig>
+ +

Step 2: Creating a Signal File and Adjusting Settings

+ +

Create Signals File

+

Create a signals.py file (you can name it anything, but signals.py is recommended). You can leave this file empty for now.

+ +

Configure apps.py

+

In your apps.py file, you need to import the signals file inside the ready function:

+
def ready(self):
+    import signals
+ +

Configure __init__.py

+

You need to specify a default_app_config variable in this file:

+
default_app_config = 'examplePlugin.apps.ExamplepluginConfig'
+ +

Create urls.py

+

Inside your app root directory, create urls.py and paste this content:

+
from django.conf.urls import url
+import views
+
+urlpatterns = [
+    url(r'^$', views.examplePlugin, name='examplePlugin'),
+]
+ +

Important: Replace examplePlugin with your plugin name. This URL definition is very important for CyberPanel to register your plugin page.

+ +

Optional Files

+

You can create these optional files for database model management:

+
    +
  • pre_install - Executed before installation of plugin
  • +
  • post_install - Executed after installation of plugin
  • +
+ +

If your file is Python code, don't forget to include this line at the top:

+
#!/usr/local/CyberCP/bin/python2
+ +

Step 3: Responding to Events

+ +

To plug into events fired by CyberPanel core, you can respond to various events happening in the core. Visit the signal file documentation for a complete list of events.

+ +

Example Events

+
    +
  • preWebsiteCreation - Fired before CyberPanel starts the creation of website
  • +
  • postWebsiteDeletion - Fired after core finished the deletion of website
  • +
+ +

Responding to Events

+

Here's how you can respond to the postWebsiteDeletion event:

+
from django.dispatch import receiver
+from django.http import HttpResponse
+from websiteFunctions.signals import postWebsiteDeletion
+from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
+
+@receiver(postWebsiteDeletion)
+def rcvr(sender, **kwargs):
+    request = kwargs['request']
+    logging.writeToFile('Hello World from Example Plugin.')
+    return HttpResponse('Hello World from Example Plugin.')
+ +

Return Values

+
    +
  • HttpResponse object - CyberPanel core will stop further processing and return your response to browser
  • +
  • int 200 - CyberPanel core will continue processing, assuming the event was successfully executed
  • +
+ +

Step 4: Packing, Shipping and Installing Plugin

+ +

Package Your Plugin

+

After completing your plugin, zip your Django app. The zip file name should be your plugin name (e.g., examplePlugin.zip), otherwise installation will fail.

+ +

Installation

+

First, upload your plugin to /usr/local/CyberCP/pluginInstaller:

+
cd /usr/local/CyberCP/pluginInstaller
+python pluginInstaller.py install --pluginName examplePlugin
+ +

Uninstall

+
cd /usr/local/CyberCP/pluginInstaller
+python pluginInstaller.py remove --pluginName examplePlugin
+ +

Beautiful Names Plugin Example

+

CyberPanel has released an official plugin called Beautiful Names that removes the admin_ prefix from Package and FTP account names. This plugin serves as a great example of how to create CyberPanel plugins.

+ +

Installation of Beautiful Names

+
cd /usr/local/CyberCP/pluginInstaller
+wget https://cyberpanel.net/beautifulNames.zip
+python pluginInstaller.py install --pluginName beautifulNames
+ +

Uninstall Beautiful Names

+
cd /usr/local/CyberCP/pluginInstaller
+python pluginInstaller.py remove --pluginName beautifulNames
+ +

Plugin Installation Facility

+

The plugin installation facility is in beta and not available with the official install yet. To install plugins, you need to install CyberPanel via the test version:

+
sh <(curl https://mirror.cyberpanel.net/install-test.sh || wget -O - https://mirror.cyberpanel.net/install-test.sh)
+ +

Additional Resources

+ + +
+ Note: This guide is based on the official CyberPanel documentation. For the most up-to-date information, always refer to the official sources. +
+
+
+ + +
+
+

CyberPanel Plugin Development Guide

+ + + +
+

How to Install Plugins

+ +

Method 1: Using the Installation Script (Recommended)

+
# Download and run the installation script
+curl -sSL https://raw.githubusercontent.com/cyberpanel/testPlugin/main/install.sh | bash
+
+# Or download first, then run
+wget https://raw.githubusercontent.com/cyberpanel/testPlugin/main/install.sh
+chmod +x install.sh
+./install.sh
+ +

Method 2: Manual Installation

+
    +
  1. Create Plugin Directory Structure +
    mkdir -p /home/cyberpanel/plugins/yourPlugin
    +mkdir -p /usr/local/CyberCP/yourPlugin
    +
  2. +
  3. Copy Plugin Files +
    cp -r yourPlugin/* /usr/local/CyberCP/yourPlugin/
    +chown -R cyberpanel:cyberpanel /usr/local/CyberCP/yourPlugin
    +chmod -R 755 /usr/local/CyberCP/yourPlugin
    +
  4. +
  5. Create Symlink +
    ln -sf /usr/local/CyberCP/yourPlugin /home/cyberpanel/plugins/yourPlugin
    +
  6. +
  7. Update Django Settings +

    Add your plugin to INSTALLED_APPS in /usr/local/CyberCP/cyberpanel/settings.py:

    +
    INSTALLED_APPS = [
    +    # ... existing apps ...
    +    'yourPlugin',
    +]
    +
  8. +
  9. Update URL Configuration +

    Add your plugin URLs in /usr/local/CyberCP/cyberpanel/urls.py:

    +
    urlpatterns = [
    +    # ... existing patterns ...
    +    path("yourPlugin/", include("yourPlugin.urls")),
    +]
    +
  10. +
  11. Run Migrations +
    cd /usr/local/CyberCP
    +python3 manage.py makemigrations yourPlugin
    +python3 manage.py migrate yourPlugin
    +
  12. +
  13. Collect Static Files +
    python3 manage.py collectstatic --noinput
    +
  14. +
  15. Restart Services +
    systemctl restart lscpd
    +systemctl restart cyberpanel
    +
  16. +
+
+ +
+

How to Uninstall Plugins

+ +

Method 1: Using the Installation Script

+
# Run with uninstall flag
+./install.sh --uninstall
+ +

Method 2: Manual Uninstallation

+
    +
  1. Remove Plugin Files +
    rm -rf /usr/local/CyberCP/yourPlugin
    +rm -f /home/cyberpanel/plugins/yourPlugin
    +
  2. +
  3. Remove from Django Settings +
    sed -i '/yourPlugin/d' /usr/local/CyberCP/cyberpanel/settings.py
    +
  4. +
  5. Remove from URLs +
    sed -i '/yourPlugin/d' /usr/local/CyberCP/cyberpanel/urls.py
    +
  6. +
  7. Restart Services +
    systemctl restart lscpd
    +systemctl restart cyberpanel
    +
  8. +
+
+ +
+

How to Add meta.xml

+

Create a meta.xml file in your plugin root directory:

+
<?xml version="1.0" encoding="UTF-8"?>
+<plugin>
+    <name>Your Plugin Name</name>
+    <type>Utility</type>
+    <description>Your plugin description</description>
+    <version>1.0.0</version>
+    <author>Your Name</author>
+    <website>https://your-website.com</website>
+    <license>MIT</license>
+    <dependencies>
+        <python>3.6+</python>
+        <django>2.2+</django>
+    </dependencies>
+    <permissions>
+        <admin>true</admin>
+        <user>false</user>
+    </permissions>
+    <settings>
+        <enable_toggle>true</enable_toggle>
+        <test_button>true</test_button>
+        <popup_messages>true</popup_messages>
+        <inline_integration>true</inline_integration>
+    </settings>
+</plugin>
+ +

Required Fields:

+
    +
  • name: Plugin display name
  • +
  • type: Plugin category (Utility, Security, Performance, etc.)
  • +
  • description: Plugin description
  • +
  • version: Plugin version
  • +
+ +

Optional Fields:

+
    +
  • author: Plugin author
  • +
  • website: Plugin website
  • +
  • license: License type
  • +
  • dependencies: Required dependencies
  • +
  • permissions: Access permissions
  • +
  • settings: Plugin-specific settings
  • +
+
+ +
+

How to Add Buttons for Pages

+ +

1. In Your Template

+
<!-- Primary Action Button -->
+<button class="btn-test" id="your-button">
+    <i class="fas fa-icon"></i>
+    Button Text
+</button>
+
+<!-- Secondary Button -->
+<a href="{% url 'yourPlugin:your_view' %}" class="btn-secondary">
+    <i class="fas fa-icon"></i>
+    Button Text
+</a>
+
+<!-- Danger Button -->
+<button class="btn-danger" id="danger-button">
+    <i class="fas fa-trash"></i>
+    Delete
+</button>
+ +

2. CSS Styles

+
.btn-test {
+    background: linear-gradient(135deg, #5856d6, #4a90e2);
+    color: white;
+    border: none;
+    padding: 12px 24px;
+    border-radius: 8px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.btn-test:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 20px rgba(88,86,214,0.3);
+}
+
+.btn-test:disabled {
+    opacity: 0.6;
+    cursor: not-allowed;
+    transform: none;
+}
+ +

3. JavaScript Event Handling

+
document.getElementById('your-button').addEventListener('click', function() {
+    // Your button logic here
+    fetch('/yourPlugin/your-endpoint/', {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json',
+            'X-CSRFToken': getCSRFToken()
+        },
+        body: JSON.stringify({data: 'value'})
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.status === 1) {
+            showNotification('success', 'Success', data.message);
+        } else {
+            showNotification('error', 'Error', data.error_message);
+        }
+    });
+});
+
+ +
+

How to Add Toggles

+ +

1. HTML Structure

+
<div class="control-group">
+    <label for="plugin-toggle" class="toggle-label">
+        Enable Feature
+    </label>
+    <label class="toggle-switch">
+        <input type="checkbox" id="plugin-toggle" {% if feature_enabled %}checked{% endif %}>
+        <span class="slider"></span>
+    </label>
+</div>
+ +

2. CSS Styles

+
.toggle-switch {
+    position: relative;
+    display: inline-block;
+    width: 60px;
+    height: 34px;
+}
+
+.toggle-switch input {
+    opacity: 0;
+    width: 0;
+    height: 0;
+}
+
+.slider {
+    position: absolute;
+    cursor: pointer;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: #ccc;
+    transition: .4s;
+    border-radius: 34px;
+}
+
+.slider:before {
+    position: absolute;
+    content: "";
+    height: 26px;
+    width: 26px;
+    left: 4px;
+    bottom: 4px;
+    background-color: white;
+    transition: .4s;
+    border-radius: 50%;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+input:checked + .slider {
+    background-color: #5856d6;
+}
+
+input:checked + .slider:before {
+    transform: translateX(26px);
+}
+ +

3. JavaScript Handling

+
document.getElementById('plugin-toggle').addEventListener('change', function() {
+    fetch('/yourPlugin/toggle/', {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json',
+            'X-CSRFToken': getCSRFToken()
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.status === 1) {
+            showNotification('success', 'Toggle Updated', data.message);
+        } else {
+            showNotification('error', 'Error', data.error_message);
+            // Revert toggle state
+            this.checked = !this.checked;
+        }
+    });
+});
+
+ +
+

How to Add Install/Uninstall Buttons

+ +

1. In Your Plugin Template

+
<div class="plugin-actions">
+    <button class="btn-install" id="install-plugin">
+        <i class="fas fa-download"></i>
+        Install Plugin
+    </button>
+    
+    <button class="btn-uninstall" id="uninstall-plugin">
+        <i class="fas fa-trash"></i>
+        Uninstall Plugin
+    </button>
+</div>
+ +

2. CSS Styles

+
.plugin-actions {
+    display: flex;
+    gap: 10px;
+    margin-top: 20px;
+}
+
+.btn-install {
+    background: #10b981;
+    color: white;
+    border: none;
+    padding: 10px 20px;
+    border-radius: 6px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+}
+
+.btn-uninstall {
+    background: #ef4444;
+    color: white;
+    border: none;
+    padding: 10px 20px;
+    border-radius: 6px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+}
+
+.btn-install:hover {
+    background: #059669;
+    transform: translateY(-2px);
+}
+
+.btn-uninstall:hover {
+    background: #dc2626;
+    transform: translateY(-2px);
+}
+ +

3. JavaScript Implementation

+
// Install button
+document.getElementById('install-plugin').addEventListener('click', function() {
+    if (confirm('Are you sure you want to install this plugin?')) {
+        this.disabled = true;
+        this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Installing...';
+        
+        fetch('/yourPlugin/install/', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                'X-CSRFToken': getCSRFToken()
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            if (data.status === 1) {
+                showNotification('success', 'Installation Complete', data.message);
+                location.reload();
+            } else {
+                showNotification('error', 'Installation Failed', data.error_message);
+            }
+        })
+        .finally(() => {
+            this.disabled = false;
+            this.innerHTML = '<i class="fas fa-download"></i> Install Plugin';
+        });
+    }
+});
+
+// Uninstall button
+document.getElementById('uninstall-plugin').addEventListener('click', function() {
+    if (confirm('Are you sure you want to uninstall this plugin? This action cannot be undone.')) {
+        this.disabled = true;
+        this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uninstalling...';
+        
+        fetch('/yourPlugin/uninstall/', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+                'X-CSRFToken': getCSRFToken()
+            }
+        })
+        .then(response => response.json())
+        .then(data => {
+            if (data.status === 1) {
+                showNotification('success', 'Uninstallation Complete', data.message);
+                setTimeout(() => location.reload(), 2000);
+            } else {
+                showNotification('error', 'Uninstallation Failed', data.error_message);
+            }
+        })
+        .finally(() => {
+            this.disabled = false;
+            this.innerHTML = '<i class="fas fa-trash"></i> Uninstall Plugin';
+        });
+    }
+});
+
+ +
+

How to Add Enable/Disable Plugin Buttons

+ +

1. Model for Plugin State

+
# models.py
+from django.db import models
+from django.contrib.auth.models import User
+
+class PluginSettings(models.Model):
+    user = models.ForeignKey(User, on_delete=models.CASCADE)
+    plugin_enabled = models.BooleanField(default=True)
+    created_at = models.DateTimeField(auto_now_add=True)
+    updated_at = models.DateTimeField(auto_now=True)
+    
+    class Meta:
+        unique_together = ['user']
+ +

2. View for Toggle

+
# views.py
+from django.http import JsonResponse
+from django.views.decorators.http import require_http_methods
+from .models import PluginSettings
+
+@require_http_methods(["POST"])
+def toggle_plugin(request):
+    try:
+        settings, created = PluginSettings.objects.get_or_create(
+            user=request.user,
+            defaults={'plugin_enabled': True}
+        )
+        
+        settings.plugin_enabled = not settings.plugin_enabled
+        settings.save()
+        
+        return JsonResponse({
+            'status': 1,
+            'enabled': settings.plugin_enabled,
+            'message': f'Plugin {"enabled" if settings.plugin_enabled else "disabled"} successfully'
+        })
+    except Exception as e:
+        return JsonResponse({'status': 0, 'error_message': str(e)})
+ +

3. Template Implementation

+
<div class="plugin-controls">
+    <label for="plugin-toggle" class="toggle-label">
+        Enable Plugin
+    </label>
+    <label class="toggle-switch">
+        <input type="checkbox" id="plugin-toggle" {% if plugin_enabled %}checked{% endif %}>
+        <span class="slider"></span>
+    </label>
+</div>
+ +

4. JavaScript Handling

+
document.getElementById('plugin-toggle').addEventListener('change', function() {
+    fetch('/yourPlugin/toggle/', {
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/json',
+            'X-CSRFToken': getCSRFToken()
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        if (data.status === 1) {
+            showNotification('success', 'Plugin Toggle', data.message);
+            // Update UI elements based on enabled state
+            updatePluginUI(data.enabled);
+        } else {
+            showNotification('error', 'Error', data.error_message);
+            this.checked = !this.checked; // Revert toggle
+        }
+    });
+});
+
+function updatePluginUI(enabled) {
+    const buttons = document.querySelectorAll('.plugin-button');
+    buttons.forEach(button => {
+        button.disabled = !enabled;
+    });
+    
+    const statusIndicator = document.querySelector('.status-indicator');
+    if (statusIndicator) {
+        statusIndicator.textContent = enabled ? 'Enabled' : 'Disabled';
+        statusIndicator.className = `status-indicator ${enabled ? 'enabled' : 'disabled'}`;
+    }
+}
+
+ +
+

How to Avoid Breaking the CyberPanel Sidebar

+ +

1. Use CyberPanel's Base Template

+

Always extend the base template:

+
{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Your Plugin - CyberPanel" %}{% endblock %}
+
+{% block content %}
+<!-- Your plugin content here -->
+{% endblock %}
+ +

2. Don't Modify the Sidebar HTML

+

Never directly modify the sidebar HTML. Instead, use CyberPanel's built-in navigation system.

+ +

3. Use Proper CSS Scoping

+
/* Good: Scoped to your plugin */
+.your-plugin-wrapper {
+    /* Your styles here */
+}
+
+/* Bad: Global styles that might affect sidebar */
+.sidebar {
+    /* Don't do this */
+}
+ +

4. Use CyberPanel's CSS Variables

+
.your-plugin-element {
+    background: var(--bg-primary, white);
+    color: var(--text-primary, #2f3640);
+    border: 1px solid var(--border-primary, #e8e9ff);
+}
+ +

5. Test Responsive Design

+

Ensure your plugin works on all screen sizes without breaking the sidebar:

+
@media (max-width: 768px) {
+    .your-plugin-wrapper {
+        padding: 15px;
+    }
+    
+    /* Don't modify sidebar behavior */
+}
+
+ +
+

How to Make Plugins Load Inline

+ +

1. Use CyberPanel's httpProc

+
# views.py
+from plogical.httpProc import httpProc
+
+def your_view(request):
+    context = {
+        'data': 'your_data',
+        'plugin_enabled': True
+    }
+    
+    proc = httpProc(request, 'yourPlugin/your_template.html', context, 'admin')
+    return proc.render()
+ +

2. Template Structure

+
{% extends "baseTemplate/index.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block title %}{% trans "Your Plugin - CyberPanel" %}{% endblock %}
+
+{% block header_scripts %}
+<style>
+    /* Your plugin-specific styles */
+    .your-plugin-wrapper {
+        background: transparent;
+        padding: 20px;
+    }
+    
+    .your-plugin-container {
+        max-width: 1200px;
+        margin: 0 auto;
+    }
+</style>
+{% endblock %}
+
+{% block content %}
+<div class="your-plugin-wrapper">
+    <div class="your-plugin-container">
+        <!-- Your plugin content here -->
+    </div>
+</div>
+{% endblock %}
+ +

3. URL Configuration

+
# urls.py
+from django.urls import path
+from . import views
+
+app_name = 'yourPlugin'
+
+urlpatterns = [
+    path('', views.your_view, name='your_view'),
+    path('settings/', views.settings_view, name='settings'),
+    # ... other URLs
+]
+ +

4. Main URLs Integration

+
# In /usr/local/CyberCP/cyberpanel/urls.py
+urlpatterns = [
+    # ... existing patterns ...
+    path("yourPlugin/", include("yourPlugin.urls")),
+]
+
+ +
+

Plugin Structure Overview

+
yourPlugin/
+├── __init__.py
+├── admin.py
+├── apps.py
+├── models.py
+├── views.py
+├── urls.py
+├── signals.py
+├── meta.xml
+├── install.sh
+├── templates/
+│   └── yourPlugin/
+│       ├── plugin_home.html
+│       ├── plugin_settings.html
+│       └── plugin_logs.html
+├── static/
+│   └── yourPlugin/
+│       ├── css/
+│       │   └── yourPlugin.css
+│       └── js/
+│           └── yourPlugin.js
+└── migrations/
+    └── __init__.py
+
+ +
+

Best Practices

+ +

1. Security

+
    +
  • Always validate user input
  • +
  • Use CSRF protection
  • +
  • Sanitize data before displaying
  • +
  • Use proper authentication decorators
  • +
+ +

2. Performance

+
    +
  • Use database indexes for frequently queried fields
  • +
  • Implement caching where appropriate
  • +
  • Optimize database queries
  • +
  • Minimize JavaScript and CSS
  • +
+ +

3. User Experience

+
    +
  • Provide clear feedback for all actions
  • +
  • Use loading states for long operations
  • +
  • Implement proper error handling
  • +
  • Make the interface responsive
  • +
+ +

4. Code Quality

+
    +
  • Follow Django best practices
  • +
  • Use meaningful variable names
  • +
  • Add proper documentation
  • +
  • Write unit tests
  • +
+ +

5. Integration

+
    +
  • Use CyberPanel's existing components
  • +
  • Follow the established design patterns
  • +
  • Maintain consistency with the UI
  • +
  • Test thoroughly before release
  • +
+
+ +
+

Troubleshooting

+ +

Common Issues

+ +

1. Plugin not showing in installed plugins

+
    +
  • Check if meta.xml exists and is valid
  • +
  • Verify the plugin is in INSTALLED_APPS
  • +
  • Ensure proper file permissions
  • +
+ +

2. Template not found errors

+
    +
  • Check template path in views.py
  • +
  • Verify template files exist
  • +
  • Ensure proper directory structure
  • +
+ +

3. Static files not loading

+
    +
  • Run python3 manage.py collectstatic
  • +
  • Check STATIC_URL configuration
  • +
  • Verify file permissions
  • +
+ +

4. Database migration errors

+
    +
  • Check model definitions
  • +
  • Run python3 manage.py makemigrations
  • +
  • Verify database connectivity
  • +
+ +

5. Permission denied errors

+
    +
  • Check file ownership (cyberpanel:cyberpanel)
  • +
  • Verify file permissions (755 for directories, 644 for files)
  • +
  • Ensure proper SELinux context if applicable
  • +
+ +

Debug Steps

+ +

1. Check CyberPanel logs

+
tail -f /home/cyberpanel/logs/cyberpanel.log
+ +

2. Check Django logs

+
tail -f /home/cyberpanel/logs/django.log
+ +

3. Verify plugin installation

+
ls -la /home/cyberpanel/plugins/
+ls -la /usr/local/CyberCP/yourPlugin/
+ +

4. Test database connectivity

+
cd /usr/local/CyberCP
+python3 manage.py shell
+ +

5. Check service status

+
systemctl status lscpd
+systemctl status cyberpanel
+
+ +
+ Conclusion: This guide provides comprehensive instructions for developing CyberPanel plugins. Follow the best practices and troubleshooting steps to ensure your plugins integrate seamlessly with CyberPanel while maintaining security and performance standards. +
+
+
+
+
+ + +{% endblock %} diff --git a/testPlugin/templates/testPlugin/plugin_home.html b/testPlugin/templates/testPlugin/plugin_home.html new file mode 100644 index 000000000..448ce1806 --- /dev/null +++ b/testPlugin/templates/testPlugin/plugin_home.html @@ -0,0 +1,566 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Test Plugin - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ +
+

+
+ +
+ {% trans "Test Plugin" %} +

+

{% trans "A comprehensive test plugin with enable/disable functionality, test button, and popup messages" %}

+
+ + +
+
+
+ + +
+ + +
+
+ + +
+
+
{{ settings.test_count|default:0 }}
+
{% trans "Test Clicks" %}
+
+ +
+
+ {% if plugin_enabled %} + + {% else %} + + {% endif %} +
+
{% trans "Plugin Status" %}
+
+ +
+
{{ recent_logs|length }}
+
{% trans "Recent Activities" %}
+
+
+ + +
+

+ + {% trans "Recent Activity" %} +

+ +
+ {% for log in recent_logs %} +
+
+ {% if 'click' in log.action %} + + {% elif 'toggle' in log.action %} + + {% elif 'settings' in log.action %} + + {% else %} + + {% endif %} +
+
+
{{ log.action|title }}
+
{{ log.message }}
+
+
{{ log.timestamp|date:"M d, H:i" }}
+
+ {% empty %} +
+ +

{% trans "No recent activity" %}

+
+ {% endfor %} +
+
+
+
+ + + + + +{% endblock %} diff --git a/testPlugin/templates/testPlugin/plugin_logs.html b/testPlugin/templates/testPlugin/plugin_logs.html new file mode 100644 index 000000000..d601b9221 --- /dev/null +++ b/testPlugin/templates/testPlugin/plugin_logs.html @@ -0,0 +1,286 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Test Plugin Logs - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ +
+

+ + {% trans "Test Plugin Logs" %} +

+

{% trans "View detailed activity logs for the test plugin" %}

+
+ + +
+
+ + + + + {% trans "Back to Plugin" %} + + + + + {% trans "Documentation" %} + +
+ + {% if logs %} + + + + + + + + + + {% for log in logs %} + + + + + + {% endfor %} + +
{% trans "Action" %}{% trans "Message" %}{% trans "Timestamp" %}
+ + {% if 'click' in log.action %} + + {% elif 'toggle' in log.action %} + + {% elif 'settings' in log.action %} + + {% elif 'visit' in log.action %} + + {% else %} + + {% endif %} + + {{ log.action|title|replace:"_":" " }} + {{ log.message }}{{ log.timestamp|date:"M d, Y H:i:s" }}
+ {% else %} +
+ +

{% trans "No Logs Found" %}

+

{% trans "No activity logs available yet. Start using the plugin to see logs here." %}

+
+ {% endif %} +
+
+
+ + +{% endblock %} diff --git a/testPlugin/templates/testPlugin/plugin_settings.html b/testPlugin/templates/testPlugin/plugin_settings.html new file mode 100644 index 000000000..c6b58db5d --- /dev/null +++ b/testPlugin/templates/testPlugin/plugin_settings.html @@ -0,0 +1,259 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Test Plugin Settings - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ +
+

+ + {% trans "Test Plugin Settings" %} +

+

{% trans "Configure your test plugin settings and preferences" %}

+
+ + +
+
+ {% csrf_token %} + +
+ + + + {% trans "This message will be displayed when you click the test button" %} + +
+ +
+ +
+ + {% if settings.plugin_enabled %} + {% trans "Enabled" %} + {% else %} + {% trans "Disabled" %} + {% endif %} + +

+ {% trans "Use the toggle switch on the main page to enable/disable the plugin" %} +

+
+
+ +
+ +
+
+
{{ settings.test_count }}
+
{% trans "Total Tests" %}
+
+
+
{{ settings.last_test_time|date:"M d" }}
+
{% trans "Last Test" %}
+
+
+
+ +
+ + + + + {% trans "Back to Plugin" %} + + + + + {% trans "Documentation" %} + +
+
+
+
+
+ + +{% endblock %} diff --git a/testPlugin/urls.py b/testPlugin/urls.py new file mode 100644 index 000000000..1935b074a --- /dev/null +++ b/testPlugin/urls.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from django.urls import path +from . import views + +app_name = 'testPlugin' + +urlpatterns = [ + path('', views.plugin_home, name='plugin_home'), + path('test/', views.test_button, name='test_button'), + path('toggle/', views.toggle_plugin, name='toggle_plugin'), + path('settings/', views.plugin_settings, name='plugin_settings'), + path('update-settings/', views.update_settings, name='update_settings'), + path('install/', views.install_plugin, name='install_plugin'), + path('uninstall/', views.uninstall_plugin, name='uninstall_plugin'), + path('logs/', views.plugin_logs, name='plugin_logs'), + path('docs/', views.plugin_docs, name='plugin_docs'), +] diff --git a/testPlugin/views.py b/testPlugin/views.py new file mode 100644 index 000000000..68e537f0d --- /dev/null +++ b/testPlugin/views.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +import json +import os +from django.shortcuts import render, get_object_or_404 +from django.http import JsonResponse, HttpResponse +from django.contrib.auth.decorators import login_required +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +from django.contrib import messages +from django.utils import timezone +from plogical.httpProc import httpProc +from .models import TestPluginSettings, TestPluginLog + + +@login_required +def plugin_home(request): + """Main plugin page with inline integration""" + try: + # Get or create plugin settings + settings, created = TestPluginSettings.objects.get_or_create( + user=request.user, + defaults={'plugin_enabled': True} + ) + + # Get recent logs + recent_logs = TestPluginLog.objects.filter(user=request.user).order_by('-timestamp')[:10] + + context = { + 'settings': settings, + 'recent_logs': recent_logs, + 'plugin_enabled': settings.plugin_enabled, + } + + # Log page visit + TestPluginLog.objects.create( + user=request.user, + action='page_visit', + message='Visited plugin home page' + ) + + proc = httpProc(request, 'testPlugin/plugin_home.html', context, 'admin') + return proc.render() + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +@require_http_methods(["POST"]) +def test_button(request): + """Handle test button click and show popup message""" + try: + settings, created = TestPluginSettings.objects.get_or_create( + user=request.user, + defaults={'plugin_enabled': True} + ) + + if not settings.plugin_enabled: + return JsonResponse({ + 'status': 0, + 'error_message': 'Plugin is disabled. Please enable it first.' + }) + + # Increment test count + settings.test_count += 1 + settings.save() + + # Create log entry + TestPluginLog.objects.create( + user=request.user, + action='test_button_click', + message=f'Test button clicked (count: {settings.test_count})' + ) + + # Prepare popup message + popup_message = { + 'type': 'success', + 'title': 'Test Successful!', + 'message': f'{settings.custom_message} (Clicked {settings.test_count} times)', + 'timestamp': timezone.now().strftime('%Y-%m-%d %H:%M:%S') + } + + return JsonResponse({ + 'status': 1, + 'popup_message': popup_message, + 'test_count': settings.test_count + }) + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +@require_http_methods(["POST"]) +def toggle_plugin(request): + """Toggle plugin enable/disable state""" + try: + settings, created = TestPluginSettings.objects.get_or_create( + user=request.user, + defaults={'plugin_enabled': True} + ) + + # Toggle the state + settings.plugin_enabled = not settings.plugin_enabled + settings.save() + + # Log the action + action = 'enabled' if settings.plugin_enabled else 'disabled' + TestPluginLog.objects.create( + user=request.user, + action='plugin_toggle', + message=f'Plugin {action}' + ) + + return JsonResponse({ + 'status': 1, + 'enabled': settings.plugin_enabled, + 'message': f'Plugin {action} successfully' + }) + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +def plugin_settings(request): + """Plugin settings page""" + try: + settings, created = TestPluginSettings.objects.get_or_create( + user=request.user, + defaults={'plugin_enabled': True} + ) + + context = { + 'settings': settings, + } + + proc = httpProc(request, 'testPlugin/plugin_settings.html', context, 'admin') + return proc.render() + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +@require_http_methods(["POST"]) +def update_settings(request): + """Update plugin settings""" + try: + settings, created = TestPluginSettings.objects.get_or_create( + user=request.user, + defaults={'plugin_enabled': True} + ) + + data = json.loads(request.body) + custom_message = data.get('custom_message', settings.custom_message) + + settings.custom_message = custom_message + settings.save() + + # Log the action + TestPluginLog.objects.create( + user=request.user, + action='settings_update', + message=f'Settings updated: custom_message="{custom_message}"' + ) + + return JsonResponse({ + 'status': 1, + 'message': 'Settings updated successfully' + }) + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +@require_http_methods(["POST"]) +def install_plugin(request): + """Install plugin (placeholder for future implementation)""" + try: + # Log the action + TestPluginLog.objects.create( + user=request.user, + action='plugin_install', + message='Plugin installation requested' + ) + + return JsonResponse({ + 'status': 1, + 'message': 'Plugin installation completed successfully' + }) + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +@require_http_methods(["POST"]) +def uninstall_plugin(request): + """Uninstall plugin (placeholder for future implementation)""" + try: + # Log the action + TestPluginLog.objects.create( + user=request.user, + action='plugin_uninstall', + message='Plugin uninstallation requested' + ) + + return JsonResponse({ + 'status': 1, + 'message': 'Plugin uninstallation completed successfully' + }) + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +def plugin_logs(request): + """View plugin logs""" + try: + logs = TestPluginLog.objects.filter(user=request.user).order_by('-timestamp')[:50] + + context = { + 'logs': logs, + } + + proc = httpProc(request, 'testPlugin/plugin_logs.html', context, 'admin') + return proc.render() + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)}) + + +@login_required +def plugin_docs(request): + """View plugin documentation""" + try: + context = {} + + proc = httpProc(request, 'testPlugin/plugin_docs.html', context, 'admin') + return proc.render() + + except Exception as e: + return JsonResponse({'status': 0, 'error_message': str(e)})