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 %}
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+ Download the plugin
+ git clone https://github.com/cyberpanel/testPlugin.git
+cd testPlugin
+
+ Run the installation script
+ chmod +x install.sh
+./install.sh
+
+ Access the plugin
+
+ URL: https://your-domain:8090/testPlugin/
+ Login with your CyberPanel admin credentials
+
+
+
+
+
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:
+
+ Check CyberPanel logs
+ tail -f /home/cyberpanel/logs/cyberpanel.log
+
+ Restart CyberPanel services
+ systemctl restart lscpd
+systemctl restart cyberpanel
+
+ Verify installation
+ ls -la /home/cyberpanel/plugins/testPlugin
+ls -la /usr/local/CyberCP/testPlugin
+
+
+
+
+ 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
+
+ Create Plugin Directory Structure
+ mkdir -p /home/cyberpanel/plugins/yourPlugin
+mkdir -p /usr/local/CyberCP/yourPlugin
+
+ 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
+
+ Create Symlink
+ ln -sf /usr/local/CyberCP/yourPlugin /home/cyberpanel/plugins/yourPlugin
+
+ Update Django Settings
+ Add your plugin to INSTALLED_APPS in /usr/local/CyberCP/cyberpanel/settings.py:
+ INSTALLED_APPS = [
+ # ... existing apps ...
+ 'yourPlugin',
+]
+
+ Update URL Configuration
+ Add your plugin URLs in /usr/local/CyberCP/cyberpanel/urls.py:
+ urlpatterns = [
+ # ... existing patterns ...
+ path("yourPlugin/", include("yourPlugin.urls")),
+]
+
+ Run Migrations
+ cd /usr/local/CyberCP
+python3 manage.py makemigrations yourPlugin
+python3 manage.py migrate yourPlugin
+
+ Collect Static Files
+ python3 manage.py collectstatic --noinput
+
+ Restart Services
+ systemctl restart lscpd
+systemctl restart cyberpanel
+
+
+
+
+
+
How to Uninstall Plugins
+
+
Method 1: Using the Installation Script
+
# Run with uninstall flag
+./install.sh --uninstall
+
+
Method 2: Manual Uninstallation
+
+ Remove Plugin Files
+ rm -rf /usr/local/CyberCP/yourPlugin
+rm -f /home/cyberpanel/plugins/yourPlugin
+
+ Remove from Django Settings
+ sed -i '/yourPlugin/d' /usr/local/CyberCP/cyberpanel/settings.py
+
+ Remove from URLs
+ sed -i '/yourPlugin/d' /usr/local/CyberCP/cyberpanel/urls.py
+
+ Restart Services
+ systemctl restart lscpd
+systemctl restart cyberpanel
+
+
+
+
+
+
+
+
+
+
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 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 "Enable Plugin" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 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 %}
+
+
+
+
+
+
+
+
+
+ {% if logs %}
+
+
+
+ {% trans "Action" %}
+ {% trans "Message" %}
+ {% trans "Timestamp" %}
+
+
+
+ {% for log in logs %}
+
+
+
+ {% 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" }}
+
+ {% endfor %}
+
+
+ {% 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 %}
+
+
+
+{% 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)})