diff --git a/CyberCP/secMiddleware.py b/CyberCP/secMiddleware.py
index 7eed86cfd..4f998c190 100644
--- a/CyberCP/secMiddleware.py
+++ b/CyberCP/secMiddleware.py
@@ -190,7 +190,8 @@ class secMiddleware:
pathActual.find('saveSpamAssassinConfigurations') > -1 or
pathActual.find('docker') > -1 or pathActual.find('cloudAPI') > -1 or
pathActual.find('verifyLogin') > -1 or pathActual.find('submitUserCreation') > -1 or
- pathActual.find('/api/') > -1 or pathActual.find('aiscanner/scheduled-scans') > -1)
+ pathActual.find('/api/') > -1 or pathActual.find('aiscanner/scheduled-scans') > -1 or
+ pathActual.find('plugins/discordWebhooks/webhook/') > -1)
if isAPIEndpoint:
# For API endpoints, still check for the most dangerous command injection characters
diff --git a/CyberCP/settings.py b/CyberCP/settings.py
index 9392a9a91..33d5c8408 100644
--- a/CyberCP/settings.py
+++ b/CyberCP/settings.py
@@ -54,7 +54,9 @@ INSTALLED_APPS = [
'mailServer', # Depends on websiteFunctions, ChildDomains
# Apps with multiple or complex dependencies
- 'emailPremium', # Depends on mailServer
+ 'emailPremium',
+ 'discordWebhooks', # Depends on mailServer
+ 'testPlugin', # Test plugin
'emailMarketing', # Depends on websiteFunctions and loginSystem
'cloudAPI', # Depends on websiteFunctions
'containerization', # Depends on websiteFunctions
@@ -126,7 +128,7 @@ DATABASES = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cyberpanel',
'USER': 'cyberpanel',
- 'PASSWORD': 'SLTUIUxqhulwsh',
+ 'PASSWORD': '1XTy1XOV0BZPnM',
'HOST': 'localhost',
'PORT': ''
},
@@ -134,7 +136,7 @@ DATABASES = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mysql',
'USER': 'root',
- 'PASSWORD': 'SLTUIUxqhulwsh',
+ 'PASSWORD': '1XTy1XOV0BZPnM',
'HOST': 'localhost',
'PORT': '',
},
@@ -211,6 +213,10 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 2147483648
# Security settings
X_FRAME_OPTIONS = 'SAMEORIGIN'
+# Login URL - CyberPanel uses root path for login
+LOGIN_URL = '/'
+LOGIN_REDIRECT_URL = '/'
+
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
\ No newline at end of file
diff --git a/CyberCP/urls.py b/CyberCP/urls.py
index 9aebb6674..83ed80688 100644
--- a/CyberCP/urls.py
+++ b/CyberCP/urls.py
@@ -40,7 +40,8 @@ urlpatterns = [
path('filemanager/', include('filemanager.urls')),
path('emailPremium/', include('emailPremium.urls')),
path('manageservices/', include('manageServices.urls')),
- path('plugins/', include('pluginHolder.urls')),
+ path('plugins/testPlugin/', include('testPlugin.urls')), path('plugins/discordWebhooks/',include('discordWebhooks.urls')),
+path('plugins/', include('pluginHolder.urls')),
path('emailMarketing/', include('emailMarketing.urls')),
path('cloudAPI/', include('cloudAPI.urls')),
path('docker/', include('dockerManager.urls')),
diff --git a/guides/INDEX.md b/guides/INDEX.md
index 94100e9d8..2e2ac389b 100644
--- a/guides/INDEX.md
+++ b/guides/INDEX.md
@@ -43,6 +43,9 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
### 🎨 Customization & Design
- **[Custom CSS Guide](CUSTOM_CSS_GUIDE.md)** - Complete guide for creating custom CSS that works with CyberPanel 2.5.5-dev design system
+### 🔌 Plugins & Extensions
+- **[Plugin System Guide](PLUGINS.md)** - Complete guide to CyberPanel plugin system, development, testPlugin reference, and plugin management
+
### 🔐 Security & Authentication
- **[2FA Authentication Guide](2FA_AUTHENTICATION_GUIDE.md)** - Complete guide for Two-Factor Authentication and WebAuthn/Passkey setup
@@ -99,6 +102,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
- **Email Marketing**: [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
- **Storage Management**: [Home Directory Management Guide](HOME_DIRECTORY_MANAGEMENT_GUIDE.md)
- **Customization & Design**: [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
+- **Plugin System**: [Plugin System Guide](PLUGINS.md)
- **Command Line Interface**: [CLI Command Reference](CLI_COMMAND_REFERENCE.md)
- **Development**: [Contributing Guide](CONTRIBUTING.md)
diff --git a/guides/PLUGINS.md b/guides/PLUGINS.md
new file mode 100644
index 000000000..4fb75dc4e
--- /dev/null
+++ b/guides/PLUGINS.md
@@ -0,0 +1,490 @@
+# CyberPanel Plugin System Guide
+
+**Author:** master3395
+**Version:** 1.0.0
+**Last Updated:** 2026-01-04
+
+---
+
+## Overview
+
+CyberPanel includes a plugin system that allows developers to extend the functionality of the control panel. Plugins can add new features, integrate with external services, and customize the user experience.
+
+This guide covers:
+- Installing and managing plugins
+- Developing your own plugins
+- Plugin structure and requirements
+- Using the testPlugin as a reference
+
+---
+
+## Table of Contents
+
+1. [Plugin Installation](#plugin-installation)
+2. [Plugin Management](#plugin-management)
+3. [Plugin Development](#plugin-development)
+4. [Plugin Structure](#plugin-structure)
+5. [TestPlugin Reference](#testplugin-reference)
+6. [Best Practices](#best-practices)
+7. [Troubleshooting](#troubleshooting)
+
+---
+
+## Plugin Installation
+
+### Prerequisites
+
+- CyberPanel installed and running
+- Admin access to CyberPanel
+- Server with appropriate permissions
+
+### Installation Steps
+
+1. **Access Plugin Manager**
+ - Log into CyberPanel as administrator
+ - Navigate to **Plugins** → **Installed Plugins**
+ - Click **Upload Plugin** button
+
+2. **Upload Plugin**
+ - Select the plugin ZIP file
+ - Click **Upload**
+ - Wait for upload to complete
+
+3. **Install Plugin**
+ - After upload, the plugin will appear in the plugin list
+ - Click **Install** button next to the plugin
+ - Installation process will:
+ - Extract plugin files to `/usr/local/CyberCP/`
+ - Add plugin to `INSTALLED_APPS` in `settings.py`
+ - Add URL routing to `urls.py`
+ - Run database migrations (if applicable)
+ - Collect static files
+ - Restart CyberPanel service
+
+4. **Verify Installation**
+ - Plugin should appear in the installed plugins list
+ - Status should show as "Installed" and "Enabled"
+ - Click **Manage** or **Settings** to access plugin interface
+
+### Manual Installation (Advanced)
+
+If automatic installation fails or you need to install manually:
+
+```bash
+# 1. Extract plugin to CyberPanel directory
+cd /home/cyberpanel/plugins
+unzip plugin-name.zip
+cd plugin-name
+
+# 2. Copy plugin to CyberPanel directory
+cp -r plugin-name /usr/local/CyberCP/
+
+# 3. Add to INSTALLED_APPS
+# Edit /usr/local/CyberCP/CyberCP/settings.py
+# Add 'pluginName', to INSTALLED_APPS list (alphabetically ordered)
+
+# 4. Add URL routing
+# Edit /usr/local/CyberCP/CyberCP/urls.py
+# Add before generic plugin route:
+# path('plugins/pluginName/', include('pluginName.urls')),
+
+# 5. Run migrations (if plugin has models)
+cd /usr/local/CyberCP
+python3 manage.py makemigrations pluginName
+python3 manage.py migrate pluginName
+
+# 6. Collect static files
+python3 manage.py collectstatic --noinput
+
+# 7. Set proper permissions
+chown -R cyberpanel:cyberpanel /usr/local/CyberCP/pluginName/
+
+# 8. Restart CyberPanel
+systemctl restart lscpd
+```
+
+---
+
+## Plugin Management
+
+### Accessing Plugins
+
+- **Plugin List**: Navigate to **Plugins** → **Installed Plugins**
+- **Plugin Settings**: Click **Manage** or **Settings** button for each plugin
+- **Plugin URL**: Typically `/plugins/pluginName/` or `/plugins/pluginName/settings/`
+
+### Enabling/Disabling Plugins
+
+Plugins are automatically enabled after installation. To disable:
+- Some plugins may have enable/disable functionality in their settings
+- To fully disable, you can remove from `INSTALLED_APPS` (advanced)
+
+### Uninstalling Plugins
+
+1. Navigate to **Plugins** → **Installed Plugins**
+2. Click **Uninstall** button (if available)
+3. Manual uninstallation:
+ - Remove from `INSTALLED_APPS` in `settings.py`
+ - Remove URL routing from `urls.py`
+ - Remove plugin directory: `rm -rf /usr/local/CyberCP/pluginName/`
+ - Restart CyberPanel: `systemctl restart lscpd`
+
+---
+
+## Plugin Development
+
+### Creating a New Plugin
+
+1. **Create Plugin Directory Structure**
+ ```
+ pluginName/
+ ├── __init__.py
+ ├── models.py # Database models (optional)
+ ├── views.py # View functions
+ ├── urls.py # URL routing
+ ├── forms.py # Forms (optional)
+ ├── utils.py # Utility functions (optional)
+ ├── admin.py # Admin interface (optional)
+ ├── templates/ # HTML templates
+ │ └── pluginName/
+ │ └── settings.html
+ ├── static/ # Static files (CSS, JS, images)
+ │ └── pluginName/
+ ├── migrations/ # Database migrations
+ │ └── __init__.py
+ ├── meta.xml # Plugin metadata (REQUIRED)
+ └── README.md # Plugin documentation
+ ```
+
+2. **Create meta.xml**
+ ```xml
+
+
+ Plugin Name
+ Utility
+ Plugin description
+ 1.0.0
+ /plugins/pluginName/
+ /plugins/pluginName/settings/
+
+ ```
+
+3. **Create URLs (urls.py)**
+ ```python
+ from django.urls import re_path
+ from . import views
+
+ app_name = 'pluginName' # Important: Register namespace
+
+ urlpatterns = [
+ re_path(r'^$', views.main_view, name='main'),
+ re_path(r'^settings/$', views.settings_view, name='settings'),
+ ]
+ ```
+
+4. **Create Views (views.py)**
+ ```python
+ from django.shortcuts import render, redirect
+ from functools import wraps
+
+ def cyberpanel_login_required(view_func):
+ """Custom decorator for CyberPanel session authentication"""
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ try:
+ userID = request.session['userID']
+ return view_func(request, *args, **kwargs)
+ except KeyError:
+ from loginSystem.views import loadLoginPage
+ return redirect(loadLoginPage)
+ return _wrapped_view
+
+ @cyberpanel_login_required
+ def main_view(request):
+ context = {
+ 'plugin_name': 'Plugin Name',
+ 'version': '1.0.0'
+ }
+ return render(request, 'pluginName/index.html', context)
+
+ @cyberpanel_login_required
+ def settings_view(request):
+ context = {
+ 'plugin_name': 'Plugin Name',
+ 'version': '1.0.0'
+ }
+ return render(request, 'pluginName/settings.html', context)
+ ```
+
+5. **Create Templates**
+ - Templates should extend `baseTemplate/index.html`
+ - Place templates in `templates/pluginName/` directory
+ ```html
+ {% extends "baseTemplate/index.html" %}
+ {% load static %}
+ {% load i18n %}
+
+ {% block title %}
+ Plugin Name Settings - {% trans "CyberPanel" %}
+ {% endblock %}
+
+ {% block content %}
+
+
Plugin Name Settings
+
+
+ {% endblock %}
+ ```
+
+6. **Package Plugin**
+ ```bash
+ cd /home/cyberpanel/plugins
+ zip -r pluginName.zip pluginName/
+ ```
+
+---
+
+## Plugin Structure
+
+### Required Files
+
+- **meta.xml**: Plugin metadata (name, version, description, URLs)
+- **__init__.py**: Python package marker
+- **urls.py**: URL routing configuration
+- **views.py**: View functions (at minimum, a main view)
+
+### Optional Files
+
+- **models.py**: Database models
+- **forms.py**: Django forms
+- **utils.py**: Utility functions
+- **admin.py**: Django admin integration
+- **templates/**: HTML templates
+- **static/**: CSS, JavaScript, images
+- **migrations/**: Database migrations
+
+### Template Requirements
+
+- Must extend `baseTemplate/index.html` (not `baseTemplate/base.html`)
+- Use Django template tags: `{% load static %}`, `{% load i18n %}`
+- Follow CyberPanel UI conventions
+
+### Authentication
+
+Plugins should use the custom `cyberpanel_login_required` decorator:
+- Checks for `request.session['userID']`
+- Redirects to login if not authenticated
+- Compatible with CyberPanel's session-based authentication
+
+---
+
+## TestPlugin Reference
+
+The **testPlugin** is a reference implementation included with CyberPanel that demonstrates:
+
+- Basic plugin structure
+- Authentication handling
+- Settings page implementation
+- Clean URL routing
+- Template inheritance
+
+### TestPlugin Location
+
+- **Source**: `/usr/local/CyberCP/testPlugin/`
+- **URL**: `/plugins/testPlugin/`
+- **Settings URL**: `/plugins/testPlugin/settings/`
+
+### TestPlugin Features
+
+1. **Main View**: Simple plugin information page
+2. **Settings View**: Plugin settings interface
+3. **Plugin Info API**: JSON endpoint for plugin information
+
+### Examining TestPlugin
+
+```bash
+# View plugin structure
+ls -la /usr/local/CyberCP/testPlugin/
+
+# View meta.xml
+cat /usr/local/CyberCP/testPlugin/meta.xml
+
+# View URLs
+cat /usr/local/CyberCP/testPlugin/urls.py
+
+# View views
+cat /usr/local/CyberCP/testPlugin/views.py
+
+# View templates
+ls -la /usr/local/CyberCP/testPlugin/templates/testPlugin/
+```
+
+### Using TestPlugin as Template
+
+1. Copy testPlugin directory:
+ ```bash
+ cp -r /usr/local/CyberCP/testPlugin /home/cyberpanel/plugins/myPlugin
+ ```
+
+2. Rename files and update content:
+ - Update `meta.xml` with your plugin information
+ - Rename template files
+ - Update views with your functionality
+ - Update URLs as needed
+
+3. Customize for your needs:
+ - Add models if you need database storage
+ - Add forms for user input
+ - Add utilities for complex logic
+ - Add static files for styling
+
+---
+
+## Best Practices
+
+### Security
+
+- Always use `cyberpanel_login_required` decorator for views
+- Validate and sanitize all user input
+- Use Django's form validation
+- Follow CyberPanel security guidelines
+- Never expose sensitive information in templates or responses
+
+### Code Organization
+
+- Keep files under 500 lines (split into modules if needed)
+- Use descriptive function and variable names
+- Add comments for complex logic
+- Follow Python PEP 8 style guide
+- Organize code into logical modules
+
+### Templates
+
+- Always extend `baseTemplate/index.html`
+- Use CyberPanel's existing CSS classes
+- Make templates mobile-friendly
+- Use Django's internationalization (`{% trans %}`)
+- Keep templates clean and readable
+
+### Database
+
+- Use Django migrations for schema changes
+- Add proper indexes for performance
+- Use transactions for multi-step operations
+- Clean up old data when uninstalling
+
+### Testing
+
+- Test plugin installation process
+- Test all plugin functionality
+- Test error handling
+- Test on multiple CyberPanel versions
+- Test plugin uninstallation
+
+### Documentation
+
+- Include README.md with installation instructions
+- Document all configuration options
+- Provide usage examples
+- Include troubleshooting section
+- Document any dependencies
+
+---
+
+## Troubleshooting
+
+### Plugin Not Appearing in List
+
+- Check `meta.xml` format (must be valid XML)
+- Verify plugin directory exists in `/home/cyberpanel/plugins/`
+- Check CyberPanel logs: `tail -f /var/log/cyberpanel/error.log`
+
+### Plugin Installation Fails
+
+- Check file permissions: `ls -la /usr/local/CyberCP/pluginName/`
+- Verify `INSTALLED_APPS` entry in `settings.py`
+- Check URL routing in `urls.py`
+- Review installation logs
+- Check Python syntax: `python3 -m py_compile pluginName/views.py`
+
+### Plugin Settings Page Not Loading
+
+- Verify URL routing is correct
+- Check template path (must be `templates/pluginName/settings.html`)
+- Verify template extends `baseTemplate/index.html`
+- Check for JavaScript errors in browser console
+- Verify authentication decorator is used
+
+### Template Not Found Error
+
+- Check template directory structure: `templates/pluginName/`
+- Verify template name in `render()` call matches file name
+- Ensure template extends correct base template
+- Check template syntax for errors
+
+### Authentication Issues
+
+- Verify `cyberpanel_login_required` decorator is used
+- Check session is active: `request.session.get('userID')`
+- Verify user is logged into CyberPanel
+- Check redirect logic in decorator
+
+### Static Files Not Loading
+
+- Run `python3 manage.py collectstatic`
+- Check static file URLs in templates
+- Verify static files are in `static/pluginName/` directory
+- Clear browser cache
+
+### Database Migration Issues
+
+- Check migrations directory exists: `migrations/__init__.py`
+- Verify models are properly defined
+- Run migrations: `python3 manage.py makemigrations pluginName`
+- Apply migrations: `python3 manage.py migrate pluginName`
+
+### Plugin Conflicts
+
+- Check for duplicate plugin names
+- Verify URL patterns don't conflict
+- Check for namespace conflicts in `urls.py`
+- Review `INSTALLED_APPS` for duplicate entries
+
+---
+
+## Plugin Examples
+
+### Available Plugins
+
+1. **testPlugin**: Reference implementation for plugin development
+2. **discordWebhooks**: Server monitoring and notifications via Discord
+3. **fail2ban**: Fail2ban security manager for CyberPanel
+
+### Plugin Repository
+
+Community plugins are available at:
+- GitHub: https://github.com/master3395/cyberpanel-plugins
+
+---
+
+## Additional Resources
+
+- **CyberPanel Documentation**: https://cyberpanel.net/KnowledgeBase/
+- **Django Documentation**: https://docs.djangoproject.com/
+- **Plugin System Source**: `/usr/local/CyberCP/pluginInstaller/`
+- **Plugin Holder**: `/usr/local/CyberCP/pluginHolder/`
+
+---
+
+## Support
+
+For plugin development questions:
+- Check existing plugins for examples
+- Review CyberPanel source code
+- Ask in CyberPanel community forum
+- Create an issue on GitHub
+
+---
+
+**Last Updated:** 2026-01-04
+**Author:** master3395
diff --git a/pluginHolder/views.py b/pluginHolder/views.py
index 25ecc2b6a..1f5a0507c 100644
--- a/pluginHolder/views.py
+++ b/pluginHolder/views.py
@@ -1,28 +1,339 @@
# -*- coding: utf-8 -*-
from django.shortcuts import render
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_http_methods
from plogical.mailUtilities import mailUtilities
import os
+import subprocess
+import shlex
+import json
from xml.etree import ElementTree
from plogical.httpProc import httpProc
+from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
+import sys
+sys.path.append('/usr/local/CyberCP')
+from pluginInstaller.pluginInstaller import pluginInstaller
+
+# Plugin state file location
+PLUGIN_STATE_DIR = '/home/cyberpanel/plugin_states'
+
+def _get_plugin_state_file(plugin_name):
+ """Get the path to the plugin state file"""
+ if not os.path.exists(PLUGIN_STATE_DIR):
+ os.makedirs(PLUGIN_STATE_DIR, mode=0o755)
+ return os.path.join(PLUGIN_STATE_DIR, plugin_name + '.state')
+
+def _is_plugin_enabled(plugin_name):
+ """Check if a plugin is enabled"""
+ state_file = _get_plugin_state_file(plugin_name)
+ if os.path.exists(state_file):
+ try:
+ with open(state_file, 'r') as f:
+ state = f.read().strip()
+ return state == 'enabled'
+ except:
+ return True # Default to enabled if file read fails
+ return True # Default to enabled if state file doesn't exist
+
+def _set_plugin_state(plugin_name, enabled):
+ """Set plugin enabled/disabled state"""
+ state_file = _get_plugin_state_file(plugin_name)
+ try:
+ with open(state_file, 'w') as f:
+ f.write('enabled' if enabled else 'disabled')
+ os.chmod(state_file, 0o644)
+ return True
+ except Exception as e:
+ logging.writeToFile(f"Error writing plugin state for {plugin_name}: {str(e)}")
+ return False
def installed(request):
mailUtilities.checkHome()
pluginPath = '/home/cyberpanel/plugins'
pluginList = []
+ errorPlugins = []
if os.path.exists(pluginPath):
for plugin in os.listdir(pluginPath):
+ # Skip files (like .zip files) - only process directories
+ pluginDir = os.path.join(pluginPath, plugin)
+ if not os.path.isdir(pluginDir):
+ continue
+
data = {}
+ # Try installed location first, then fallback to source location
completePath = '/usr/local/CyberCP/' + plugin + '/meta.xml'
- pluginMetaData = ElementTree.parse(completePath)
+ sourcePath = os.path.join(pluginDir, 'meta.xml')
+
+ # Determine which meta.xml to use
+ metaXmlPath = None
+ if os.path.exists(completePath):
+ metaXmlPath = completePath
+ elif os.path.exists(sourcePath):
+ # Plugin not installed but has source meta.xml - use it
+ metaXmlPath = sourcePath
+
+ # Add error handling to prevent 500 errors
+ try:
+ if metaXmlPath is None:
+ # No meta.xml found in either location - skip silently
+ continue
+
+ pluginMetaData = ElementTree.parse(metaXmlPath)
+ root = pluginMetaData.getroot()
+
+ # Validate required fields exist (handle both and formats)
+ name_elem = root.find('name')
+ type_elem = root.find('type')
+ desc_elem = root.find('description')
+ version_elem = root.find('version')
+
+ # Type field is optional (testPlugin doesn't have it)
+ if name_elem is None or desc_elem is None or version_elem is None:
+ errorPlugins.append({'name': plugin, 'error': 'Missing required metadata fields (name, description, or version)'})
+ logging.writeToFile(f"Plugin {plugin}: Missing required metadata fields in meta.xml")
+ continue
+
+ # Check if text is None (empty elements)
+ if name_elem.text is None or desc_elem.text is None or version_elem.text is None:
+ errorPlugins.append({'name': plugin, 'error': 'Empty metadata fields'})
+ logging.writeToFile(f"Plugin {plugin}: Empty metadata fields in meta.xml")
+ continue
+
+ data['name'] = name_elem.text
+ data['type'] = type_elem.text if type_elem is not None and type_elem.text is not None else 'Plugin'
+ data['desc'] = desc_elem.text
+ data['version'] = version_elem.text
+ data['plugin_dir'] = plugin # Plugin directory name
+ data['installed'] = os.path.exists(completePath) # True if installed, False if only in source
+
+ # Get plugin enabled state (only for installed plugins)
+ if data['installed']:
+ data['enabled'] = _is_plugin_enabled(plugin)
+ else:
+ data['enabled'] = False
+
+ # Extract settings URL or main URL for "Manage" button
+ settings_url_elem = root.find('settings_url')
+ url_elem = root.find('url')
+
+ # Priority: settings_url > url > default pattern
+ if settings_url_elem is not None and settings_url_elem.text:
+ data['manage_url'] = settings_url_elem.text
+ elif url_elem is not None and url_elem.text:
+ data['manage_url'] = url_elem.text
+ else:
+ # Default: try /plugins/{plugin_dir}/settings/ or /plugins/{plugin_dir}/
+ # Only set if plugin is installed (we can't know if the URL exists otherwise)
+ if os.path.exists(completePath):
+ data['manage_url'] = f'/plugins/{plugin}/settings/'
+ else:
+ data['manage_url'] = None
- data['name'] = pluginMetaData.find('name').text
- data['type'] = pluginMetaData.find('type').text
- data['desc'] = pluginMetaData.find('description').text
- data['version'] = pluginMetaData.find('version').text
-
- pluginList.append(data)
+ pluginList.append(data)
+ except ElementTree.ParseError as e:
+ errorPlugins.append({'name': plugin, 'error': f'XML parse error: {str(e)}'})
+ logging.writeToFile(f"Plugin {plugin}: XML parse error - {str(e)}")
+ continue
+ except Exception as e:
+ errorPlugins.append({'name': plugin, 'error': f'Error loading plugin: {str(e)}'})
+ logging.writeToFile(f"Plugin {plugin}: Error loading - {str(e)}")
+ continue
proc = httpProc(request, 'pluginHolder/plugins.html',
- {'plugins': pluginList}, 'admin')
+ {'plugins': pluginList, 'error_plugins': errorPlugins}, 'admin')
return proc.render()
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def install_plugin(request, plugin_name):
+ """Install a plugin"""
+ try:
+ # Check if plugin source exists
+ pluginSource = '/home/cyberpanel/plugins/' + plugin_name
+ if not os.path.exists(pluginSource):
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Plugin source not found: {plugin_name}'
+ }, status=404)
+
+ # Check if already installed
+ pluginInstalled = '/usr/local/CyberCP/' + plugin_name
+ if os.path.exists(pluginInstalled):
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Plugin already installed: {plugin_name}'
+ }, status=400)
+
+ # Create zip file for installation (pluginInstaller expects a zip)
+ import tempfile
+ import shutil
+ temp_dir = tempfile.mkdtemp()
+ zip_path = os.path.join(temp_dir, plugin_name + '.zip')
+
+ # Create zip from source directory
+ shutil.make_archive(os.path.join(temp_dir, plugin_name), 'zip', pluginSource)
+
+ # Verify zip file was created
+ if not os.path.exists(zip_path):
+ shutil.rmtree(temp_dir, ignore_errors=True)
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Failed to create zip file for {plugin_name}'
+ }, status=500)
+
+ # Copy zip to current directory (pluginInstaller expects it in cwd)
+ original_cwd = os.getcwd()
+ os.chdir(temp_dir)
+
+ try:
+ # Verify zip file exists in current directory
+ zip_file = plugin_name + '.zip'
+ if not os.path.exists(zip_file):
+ raise Exception(f'Zip file {zip_file} not found in temp directory')
+
+ # Install using pluginInstaller
+ pluginInstaller.installPlugin(plugin_name)
+
+ # Verify plugin was actually installed
+ pluginInstalled = '/usr/local/CyberCP/' + plugin_name
+ if not os.path.exists(pluginInstalled):
+ raise Exception(f'Plugin installation failed: {pluginInstalled} does not exist after installation')
+
+ # Set plugin to enabled by default after installation
+ _set_plugin_state(plugin_name, True)
+
+ return JsonResponse({
+ 'success': True,
+ 'message': f'Plugin {plugin_name} installed successfully'
+ })
+ finally:
+ os.chdir(original_cwd)
+ # Cleanup
+ shutil.rmtree(temp_dir, ignore_errors=True)
+
+ except Exception as e:
+ logging.writeToFile(f"Error installing plugin {plugin_name}: {str(e)}")
+ return JsonResponse({
+ 'success': False,
+ 'error': str(e)
+ }, status=500)
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def uninstall_plugin(request, plugin_name):
+ """Uninstall a plugin - but keep source files and settings"""
+ try:
+ # Check if plugin is installed
+ pluginInstalled = '/usr/local/CyberCP/' + plugin_name
+ if not os.path.exists(pluginInstalled):
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Plugin not installed: {plugin_name}'
+ }, status=404)
+
+ # Custom uninstall that keeps source files
+ # We need to remove from settings.py, urls.py, and remove installed directory
+ # but NOT remove from /home/cyberpanel/plugins/
+
+ # Remove from settings.py
+ pluginInstaller.removeFromSettings(plugin_name)
+
+ # Remove from URLs
+ pluginInstaller.removeFromURLs(plugin_name)
+
+ # Remove interface link
+ pluginInstaller.removeInterfaceLink(plugin_name)
+
+ # Remove migrations if enabled
+ if pluginInstaller.migrationsEnabled(plugin_name):
+ pluginInstaller.removeMigrations(plugin_name)
+
+ # Remove installed directory (but keep source in /home/cyberpanel/plugins/)
+ pluginInstaller.removeFiles(plugin_name)
+
+ # DON'T call informCyberPanelRemoval - we want to keep the source directory
+ # so users can reinstall the plugin later
+
+ # Restart service
+ pluginInstaller.restartGunicorn()
+
+ # Keep state file - we want to remember if it was enabled/disabled
+ # So user can reinstall and have same state
+
+ return JsonResponse({
+ 'success': True,
+ 'message': f'Plugin {plugin_name} uninstalled successfully (source files and settings preserved)'
+ })
+
+ except Exception as e:
+ logging.writeToFile(f"Error uninstalling plugin {plugin_name}: {str(e)}")
+ return JsonResponse({
+ 'success': False,
+ 'error': str(e)
+ }, status=500)
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def enable_plugin(request, plugin_name):
+ """Enable a plugin"""
+ try:
+ # Check if plugin is installed
+ pluginInstalled = '/usr/local/CyberCP/' + plugin_name
+ if not os.path.exists(pluginInstalled):
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Plugin not installed: {plugin_name}'
+ }, status=404)
+
+ # Set plugin state to enabled
+ if _set_plugin_state(plugin_name, True):
+ return JsonResponse({
+ 'success': True,
+ 'message': f'Plugin {plugin_name} enabled successfully'
+ })
+ else:
+ return JsonResponse({
+ 'success': False,
+ 'error': 'Failed to update plugin state'
+ }, status=500)
+
+ except Exception as e:
+ logging.writeToFile(f"Error enabling plugin {plugin_name}: {str(e)}")
+ return JsonResponse({
+ 'success': False,
+ 'error': str(e)
+ }, status=500)
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def disable_plugin(request, plugin_name):
+ """Disable a plugin"""
+ try:
+ # Check if plugin is installed
+ pluginInstalled = '/usr/local/CyberCP/' + plugin_name
+ if not os.path.exists(pluginInstalled):
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Plugin not installed: {plugin_name}'
+ }, status=404)
+
+ # Set plugin state to disabled
+ if _set_plugin_state(plugin_name, False):
+ return JsonResponse({
+ 'success': True,
+ 'message': f'Plugin {plugin_name} disabled successfully'
+ })
+ else:
+ return JsonResponse({
+ 'success': False,
+ 'error': 'Failed to update plugin state'
+ }, status=500)
+
+ except Exception as e:
+ logging.writeToFile(f"Error disabling plugin {plugin_name}: {str(e)}")
+ return JsonResponse({
+ 'success': False,
+ 'error': str(e)
+ }, status=500)
diff --git a/pluginInstaller/pluginInstaller.py b/pluginInstaller/pluginInstaller.py
index 114a9fd64..960017370 100644
--- a/pluginInstaller/pluginInstaller.py
+++ b/pluginInstaller/pluginInstaller.py
@@ -20,6 +20,7 @@ class pluginInstaller:
Generate URL pattern compatible with both Django 2.x and 3.x+
Django 2.x uses url() with regex patterns
Django 3.x+ prefers path() with simpler patterns
+ Plugins are routed under /plugins/pluginName/ to match meta.xml URLs
"""
try:
django_version = django.get_version()
@@ -28,17 +29,17 @@ class pluginInstaller:
pluginInstaller.stdOut(f"Django version detected: {django_version}")
if major_version >= 3:
- # Django 3.x+ - use path() syntax
+ # Django 3.x+ - use path() syntax with /plugins/ prefix
pluginInstaller.stdOut(f"Using path() syntax for Django 3.x+ compatibility")
- return " path('" + pluginName + "/',include('" + pluginName + ".urls')),\n"
+ return " path('plugins/" + pluginName + "/',include('" + pluginName + ".urls')),\n"
else:
- # Django 2.x - use url() syntax with regex
+ # Django 2.x - use url() syntax with regex and /plugins/ prefix
pluginInstaller.stdOut(f"Using url() syntax for Django 2.x compatibility")
- return " url(r'^" + pluginName + "/',include('" + pluginName + ".urls')),\n"
+ return " url(r'^plugins/" + pluginName + "/',include('" + pluginName + ".urls')),\n"
except Exception as e:
# Fallback to modern path() syntax if version detection fails
pluginInstaller.stdOut(f"Django version detection failed: {str(e)}, using path() syntax as fallback")
- return " path('" + pluginName + "/',include('" + pluginName + ".urls')),\n"
+ return " path('plugins/" + pluginName + "/',include('" + pluginName + ".urls')),\n"
@staticmethod
def stdOut(message):
@@ -59,8 +60,14 @@ class pluginInstaller:
@staticmethod
def extractPlugin(pluginName):
pathToPlugin = pluginName + '.zip'
- command = 'unzip ' + pathToPlugin + ' -d /usr/local/CyberCP'
- subprocess.call(shlex.split(command))
+ command = 'unzip -o ' + pathToPlugin + ' -d /usr/local/CyberCP'
+ result = subprocess.run(shlex.split(command), capture_output=True, text=True)
+ if result.returncode != 0:
+ raise Exception(f"Failed to extract plugin {pluginName}: {result.stderr}")
+ # Verify extraction succeeded
+ pluginPath = '/usr/local/CyberCP/' + pluginName
+ if not os.path.exists(pluginPath):
+ raise Exception(f"Plugin extraction failed: {pluginPath} does not exist after extraction")
@staticmethod
def upgradingSettingsFile(pluginName):
@@ -78,16 +85,38 @@ class pluginInstaller:
@staticmethod
def upgradingURLs(pluginName):
+ """
+ Add plugin URL pattern to urls.py
+ Plugin URLs must be inserted BEFORE the generic 'plugins/' line
+ to ensure proper route matching (more specific routes first)
+ """
data = open("/usr/local/CyberCP/CyberCP/urls.py", 'r').readlines()
writeToFile = open("/usr/local/CyberCP/CyberCP/urls.py", 'w')
+ urlPatternAdded = False
for items in data:
- if items.find("manageservices") > -1:
+ # Insert plugin URL BEFORE the generic 'plugins/' line
+ # This ensures more specific routes are matched first
+ if items.find("path('plugins/', include('pluginHolder.urls'))") > -1 or items.find("path(\"plugins/\", include('pluginHolder.urls'))") > -1:
+ if not urlPatternAdded:
+ writeToFile.writelines(pluginInstaller.getUrlPattern(pluginName))
+ urlPatternAdded = True
writeToFile.writelines(items)
- writeToFile.writelines(pluginInstaller.getUrlPattern(pluginName))
else:
writeToFile.writelines(items)
+ # Fallback: if 'plugins/' line not found, insert after 'manageservices'
+ if not urlPatternAdded:
+ pluginInstaller.stdOut(f"Warning: 'plugins/' line not found, using fallback insertion after 'manageservices'")
+ writeToFile.close()
+ writeToFile = open("/usr/local/CyberCP/CyberCP/urls.py", 'w')
+ for items in data:
+ if items.find("manageservices") > -1:
+ writeToFile.writelines(items)
+ writeToFile.writelines(pluginInstaller.getUrlPattern(pluginName))
+ else:
+ writeToFile.writelines(items)
+
writeToFile.close()
@staticmethod
diff --git a/testPlugin/meta.xml b/testPlugin/meta.xml
index 5f6f1bae0..56ba394f1 100644
--- a/testPlugin/meta.xml
+++ b/testPlugin/meta.xml
@@ -2,8 +2,8 @@
Test Plugin
Utility
- A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration
1.0.0
+ A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration
CyberPanel Development Team
https://github.com/cyberpanel/testPlugin
MIT
@@ -21,4 +21,6 @@
true
true
+ /plugins/testPlugin/
+ /plugins/testPlugin/settings/
diff --git a/testPlugin/templates/testPlugin/index.html b/testPlugin/templates/testPlugin/index.html
new file mode 100644
index 000000000..ed5250a1e
--- /dev/null
+++ b/testPlugin/templates/testPlugin/index.html
@@ -0,0 +1,71 @@
+{% extends "baseTemplate/base.html" %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}
+ Test Plugin - {% trans "CyberPanel" %}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% trans "Plugin Name" %}
+ {{ plugin_name }}
+
+
+
+
+
+
+
+
+
+ {% trans "Version" %}
+ {{ version }}
+
+
+
+
+
+
+
{% trans "Plugin Information" %}
+
{{ description }}
+
+
+
+
+
+
+
+ {% trans "Test Plugin is working correctly!" %}
+
+
{% trans "This is a test plugin created for testing CyberPanel plugin functionality." %}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/testPlugin/templates/testPlugin/settings.html b/testPlugin/templates/testPlugin/settings.html
new file mode 100644
index 000000000..e06f901f4
--- /dev/null
+++ b/testPlugin/templates/testPlugin/settings.html
@@ -0,0 +1,165 @@
+{% extends "baseTemplate/index.html" %}
+{% load static %}
+{% load i18n %}
+
+{% block title %}
+ Test Plugin Settings - {% trans "CyberPanel" %}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
{% trans "Plugin Information" %}
+
+ - {% trans "Name" %}: {{ plugin_name }}
+ - {% trans "Version" %}: {{ version }}
+ - {% trans "Status" %}: {% trans "Active" %}
+
+
+
+
+
+
+
+
+
+
+
{% trans "Plugin is Active" %}
+
{% trans "The Test Plugin is installed and working correctly." %}
+
+
+
+
+
+
+
+
+
+ {% trans "Plugin Name" %}
+ {{ plugin_name }}
+
+
+
+
+
+
+
+
+
+ {% trans "Version" %}
+ {{ version }}
+
+
+
+
+
+
+
+
+
+
+
{{ description }}
+
{% trans "This is a test plugin created for testing CyberPanel plugin functionality. You can use this plugin to verify that the plugin system is working correctly." %}
+
+
{% trans "Features" %}
+
+ - {% trans "Enable/disable functionality" %}
+ - {% trans "Test button" %}
+ - {% trans "Popup messages" %}
+ - {% trans "Inline integration" %}
+ - {% trans "Settings page" %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/testPlugin/urls.py b/testPlugin/urls.py
index af0f720eb..8c2a41a90 100644
--- a/testPlugin/urls.py
+++ b/testPlugin/urls.py
@@ -1,18 +1,8 @@
-# -*- 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'),
- path('security/', views.security_info, name='security_info'),
+ path('', views.test_plugin_view, name='testPlugin'),
+ path('info/', views.plugin_info_view, name='testPluginInfo'),
+ path('settings/', views.settings_view, name='testPluginSettings'),
]
diff --git a/testPlugin/views.py b/testPlugin/views.py
index 395ab60fe..07bc88899 100644
--- a/testPlugin/views.py
+++ b/testPlugin/views.py
@@ -1,324 +1,54 @@
-# -*- 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 django.core.cache import cache
-from plogical.httpProc import httpProc
-from .models import TestPluginSettings, TestPluginLog
-from .security import secure_view, admin_required, SecurityManager
+from django.shortcuts import render, redirect
+from django.http import JsonResponse
+from functools import wraps
+def cyberpanel_login_required(view_func):
+ """
+ Custom decorator that checks for CyberPanel session userID
+ """
+ @wraps(view_func)
+ def _wrapped_view(request, *args, **kwargs):
+ try:
+ userID = request.session['userID']
+ # User is authenticated via CyberPanel session
+ return view_func(request, *args, **kwargs)
+ except KeyError:
+ # Not logged in, redirect to login
+ return redirect('/')
+ return _wrapped_view
-@admin_required
-@secure_view(require_csrf=False, rate_limit=True, log_activity=True)
-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 (limit to user's own logs for security)
- 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:
- SecurityManager.log_security_event(request, f"Error in plugin_home: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while loading the page.'})
+@cyberpanel_login_required
+def test_plugin_view(request):
+ """
+ Main view for the test plugin
+ """
+ context = {
+ 'plugin_name': 'Test Plugin',
+ 'version': '1.0.0',
+ 'description': 'A simple test plugin for CyberPanel'
+ }
+ return render(request, 'testPlugin/index.html', context)
+@cyberpanel_login_required
+def plugin_info_view(request):
+ """
+ API endpoint for plugin information
+ """
+ return JsonResponse({
+ 'plugin_name': 'Test Plugin',
+ 'version': '1.0.0',
+ 'status': 'active',
+ 'description': 'A simple test plugin for CyberPanel testing'
+ })
-@admin_required
-@secure_view(require_csrf=True, rate_limit=True, log_activity=True)
-@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:
- SecurityManager.log_security_event(request, "Test button clicked while plugin disabled", "security_violation")
- return JsonResponse({
- 'status': 0,
- 'error_message': 'Plugin is disabled. Please enable it first.'
- })
-
- # Rate limiting for test button (max 10 clicks per minute)
- test_key = f"test_button_{request.user.id}"
- test_count = cache.get(test_key, 0)
- if test_count >= 10:
- SecurityManager.record_failed_attempt(request, "Test button rate limit exceeded")
- return JsonResponse({
- 'status': 0,
- 'error_message': 'Too many test button clicks. Please wait before trying again.'
- }, status=429)
-
- cache.set(test_key, test_count + 1, 60) # 1 minute window
-
- # 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})'
- )
-
- # Sanitize custom message
- safe_message = SecurityManager.sanitize_input(settings.custom_message)
-
- # Prepare popup message
- popup_message = {
- 'type': 'success',
- 'title': 'Test Successful!',
- 'message': f'{safe_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:
- SecurityManager.log_security_event(request, f"Error in test_button: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while processing the test.'})
-
-
-@admin_required
-@secure_view(require_csrf=True, rate_limit=True, log_activity=True)
-@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}'
- )
-
- SecurityManager.log_security_event(request, f"Plugin {action} by user", "plugin_toggle")
-
- return JsonResponse({
- 'status': 1,
- 'enabled': settings.plugin_enabled,
- 'message': f'Plugin {action} successfully'
- })
-
- except Exception as e:
- SecurityManager.log_security_event(request, f"Error in toggle_plugin: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while toggling the plugin.'})
-
-
-@admin_required
-@secure_view(require_csrf=False, rate_limit=True, log_activity=True)
-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:
- SecurityManager.log_security_event(request, f"Error in plugin_settings: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while loading settings.'})
-
-
-@admin_required
-@secure_view(require_csrf=True, rate_limit=True, log_activity=True)
-@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)
-
- # Validate and sanitize input
- is_valid, error_msg = SecurityManager.validate_input(custom_message, 'custom_message', 1000)
- if not is_valid:
- SecurityManager.record_failed_attempt(request, f"Invalid input: {error_msg}")
- return JsonResponse({
- 'status': 0,
- 'error_message': f'Invalid input: {error_msg}'
- }, status=400)
-
- # Sanitize the message
- custom_message = SecurityManager.sanitize_input(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[:50]}..."'
- )
-
- SecurityManager.log_security_event(request, "Settings updated successfully", "settings_update")
-
- return JsonResponse({
- 'status': 1,
- 'message': 'Settings updated successfully'
- })
-
- except json.JSONDecodeError:
- SecurityManager.record_failed_attempt(request, "Invalid JSON in settings update")
- return JsonResponse({
- 'status': 0,
- 'error_message': 'Invalid data format. Please try again.'
- }, status=400)
- except Exception as e:
- SecurityManager.log_security_event(request, f"Error in update_settings: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while updating settings.'})
-
-
-@admin_required
-@secure_view(require_csrf=True, rate_limit=True, log_activity=True)
-@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'
- )
-
- SecurityManager.log_security_event(request, "Plugin installation requested", "plugin_install")
-
- return JsonResponse({
- 'status': 1,
- 'message': 'Plugin installation completed successfully'
- })
-
- except Exception as e:
- SecurityManager.log_security_event(request, f"Error in install_plugin: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred during installation.'})
-
-
-@admin_required
-@secure_view(require_csrf=True, rate_limit=True, log_activity=True)
-@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'
- )
-
- SecurityManager.log_security_event(request, "Plugin uninstallation requested", "plugin_uninstall")
-
- return JsonResponse({
- 'status': 1,
- 'message': 'Plugin uninstallation completed successfully'
- })
-
- except Exception as e:
- SecurityManager.log_security_event(request, f"Error in uninstall_plugin: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred during uninstallation.'})
-
-
-@admin_required
-@secure_view(require_csrf=False, rate_limit=True, log_activity=True)
-def plugin_logs(request):
- """View plugin logs"""
- try:
- # Only show logs for the current user (security isolation)
- 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:
- SecurityManager.log_security_event(request, f"Error in plugin_logs: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while loading logs.'})
-
-
-@admin_required
-@secure_view(require_csrf=False, rate_limit=True, log_activity=True)
-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:
- SecurityManager.log_security_event(request, f"Error in plugin_docs: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while loading documentation.'})
-
-
-@admin_required
-@secure_view(require_csrf=False, rate_limit=True, log_activity=True)
-def security_info(request):
- """View security information"""
- try:
- context = {}
-
- proc = httpProc(request, 'testPlugin/security_info.html', context, 'admin')
- return proc.render()
-
- except Exception as e:
- SecurityManager.log_security_event(request, f"Error in security_info: {str(e)}", "view_error")
- return JsonResponse({'status': 0, 'error_message': 'An error occurred while loading security information.'})
+@cyberpanel_login_required
+def settings_view(request):
+ """
+ Settings page for the test plugin
+ """
+ context = {
+ 'plugin_name': 'Test Plugin',
+ 'version': '1.0.0',
+ 'description': 'A simple test plugin for CyberPanel'
+ }
+ return render(request, 'testPlugin/settings.html', context)