mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-10-26 00:36:34 +02:00
Add better plugin description + new example plugin
Add better plugin description + new example plugin
This commit is contained in:
2
testPlugin/__init__.py
Normal file
2
testPlugin/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
default_app_config = 'testPlugin.apps.TestPluginConfig'
|
||||
20
testPlugin/admin.py
Normal file
20
testPlugin/admin.py
Normal file
@@ -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'
|
||||
11
testPlugin/apps.py
Normal file
11
testPlugin/apps.py
Normal file
@@ -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
|
||||
305
testPlugin/install.sh
Normal file
305
testPlugin/install.sh
Normal file
@@ -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 "$@"
|
||||
24
testPlugin/meta.xml
Normal file
24
testPlugin/meta.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plugin>
|
||||
<name>Test Plugin</name>
|
||||
<type>Utility</type>
|
||||
<description>A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration</description>
|
||||
<version>1.0.0</version>
|
||||
<author>CyberPanel Development Team</author>
|
||||
<website>https://github.com/cyberpanel/testPlugin</website>
|
||||
<license>MIT</license>
|
||||
<dependencies>
|
||||
<python>3.6+</python>
|
||||
<django>2.2+</django>
|
||||
</dependencies>
|
||||
<permissions>
|
||||
<admin>true</admin>
|
||||
<user>false</user>
|
||||
</permissions>
|
||||
<settings>
|
||||
<enable_toggle>true</enable_toggle>
|
||||
<test_button>true</test_button>
|
||||
<popup_messages>true</popup_messages>
|
||||
<inline_integration>true</inline_integration>
|
||||
</settings>
|
||||
</plugin>
|
||||
35
testPlugin/models.py
Normal file
35
testPlugin/models.py
Normal file
@@ -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}"
|
||||
15
testPlugin/signals.py
Normal file
15
testPlugin/signals.py
Normal file
@@ -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
|
||||
)
|
||||
336
testPlugin/static/testPlugin/css/testPlugin.css
Normal file
336
testPlugin/static/testPlugin/css/testPlugin.css
Normal file
@@ -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;
|
||||
}
|
||||
323
testPlugin/static/testPlugin/js/testPlugin.js
Normal file
323
testPlugin/static/testPlugin/js/testPlugin.js
Normal file
@@ -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 = '<i class="fas fa-spinner fa-spin"></i> 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 = `
|
||||
<button class="popup-close" onclick="this.parentElement.remove()">×</button>
|
||||
<div class="popup-title">${title}</div>
|
||||
<div class="popup-content">${message}</div>
|
||||
<div class="popup-time">${new Date().toLocaleTimeString()}</div>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="notification-title">${title}</div>
|
||||
<div class="notification-content">${message}</div>
|
||||
`;
|
||||
|
||||
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 ?
|
||||
'<i class="fas fa-check-circle"></i> Enabled' :
|
||||
'<i class="fas fa-times-circle"></i> 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;
|
||||
1356
testPlugin/templates/testPlugin/plugin_docs.html
Normal file
1356
testPlugin/templates/testPlugin/plugin_docs.html
Normal file
File diff suppressed because it is too large
Load Diff
566
testPlugin/templates/testPlugin/plugin_home.html
Normal file
566
testPlugin/templates/testPlugin/plugin_home.html
Normal file
@@ -0,0 +1,566 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans "Test Plugin - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
/* Test Plugin Specific Styles */
|
||||
.test-plugin-wrapper {
|
||||
background: transparent;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-plugin-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Page Header */
|
||||
.plugin-header {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.plugin-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #2f3640);
|
||||
margin: 0 0 10px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.plugin-header .icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: #5856d6;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
box-shadow: 0 4px 12px rgba(88,86,214,0.3);
|
||||
}
|
||||
|
||||
.plugin-header p {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Control Panel */
|
||||
.control-panel {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #5856d6;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.btn-test {
|
||||
background: linear-gradient(135deg, #5856d6, #4a90e2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-test:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(88,86,214,0.3);
|
||||
}
|
||||
|
||||
.btn-test:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Stats Cards */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #5856d6;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Recent Logs */
|
||||
.logs-section {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.logs-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.log-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.log-icon.info {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.log-icon.success {
|
||||
background: #e8f5e8;
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.log-icon.warning {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.log-action {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #2f3640);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary, #9ca3af);
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
/* Popup Message Styles */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.test-plugin-wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.popup-message {
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="test-plugin-wrapper">
|
||||
<div class="test-plugin-container">
|
||||
<!-- Plugin Header -->
|
||||
<div class="plugin-header">
|
||||
<h1>
|
||||
<div class="icon">
|
||||
<i class="fas fa-vial"></i>
|
||||
</div>
|
||||
{% trans "Test Plugin" %}
|
||||
</h1>
|
||||
<p>{% trans "A comprehensive test plugin with enable/disable functionality, test button, and popup messages" %}</p>
|
||||
</div>
|
||||
|
||||
<!-- Control Panel -->
|
||||
<div class="control-panel">
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label for="plugin-toggle" style="font-weight: 600; color: var(--text-primary, #2f3640);">
|
||||
{% trans "Enable Plugin" %}
|
||||
</label>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="plugin-toggle" {% if plugin_enabled %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button class="btn-test" id="test-button" {% if not plugin_enabled %}disabled{% endif %}>
|
||||
<i class="fas fa-play"></i>
|
||||
{% trans "Test Button" %}
|
||||
</button>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_settings' %}" class="btn-secondary">
|
||||
<i class="fas fa-cog"></i>
|
||||
{% trans "Settings" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_logs' %}" class="btn-secondary">
|
||||
<i class="fas fa-list"></i>
|
||||
{% trans "Logs" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
|
||||
<i class="fas fa-book"></i>
|
||||
{% trans "Documentation" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="test-count">{{ settings.test_count|default:0 }}</div>
|
||||
<div class="stat-label">{% trans "Test Clicks" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">
|
||||
{% if plugin_enabled %}
|
||||
<i class="fas fa-check-circle" style="color: #10b981;"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-times-circle" style="color: #ef4444;"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="stat-label">{% trans "Plugin Status" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ recent_logs|length }}</div>
|
||||
<div class="stat-label">{% trans "Recent Activities" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Logs -->
|
||||
<div class="logs-section">
|
||||
<h3 style="margin-bottom: 20px; color: var(--text-primary, #2f3640);">
|
||||
<i class="fas fa-history" style="margin-right: 8px; color: #5856d6;"></i>
|
||||
{% trans "Recent Activity" %}
|
||||
</h3>
|
||||
|
||||
<div class="logs-list">
|
||||
{% for log in recent_logs %}
|
||||
<div class="log-item">
|
||||
<div class="log-icon {% if 'error' in log.action %}warning{% elif 'success' in log.action or 'click' in log.action %}success{% else %}info{% endif %}">
|
||||
{% if 'click' in log.action %}
|
||||
<i class="fas fa-mouse-pointer"></i>
|
||||
{% elif 'toggle' in log.action %}
|
||||
<i class="fas fa-toggle-on"></i>
|
||||
{% elif 'settings' in log.action %}
|
||||
<i class="fas fa-cog"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-info-circle"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="log-content">
|
||||
<div class="log-action">{{ log.action|title }}</div>
|
||||
<div class="log-message">{{ log.message }}</div>
|
||||
</div>
|
||||
<div class="log-time">{{ log.timestamp|date:"M d, H:i" }}</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div style="text-align: center; padding: 40px; color: var(--text-secondary, #64748b);">
|
||||
<i class="fas fa-inbox" style="font-size: 48px; margin-bottom: 16px; opacity: 0.5;"></i>
|
||||
<p>{% trans "No recent activity" %}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup Message Container -->
|
||||
<div id="popup-container"></div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const toggleSwitch = document.getElementById('plugin-toggle');
|
||||
const testButton = document.getElementById('test-button');
|
||||
const testCountElement = document.getElementById('test-count');
|
||||
|
||||
// Toggle switch functionality
|
||||
toggleSwitch.addEventListener('change', function() {
|
||||
fetch('{% url "testPlugin:toggle_plugin" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 1) {
|
||||
testButton.disabled = !data.enabled;
|
||||
showPopup('success', 'Plugin Toggle', data.message);
|
||||
location.reload(); // Refresh to update UI
|
||||
} else {
|
||||
showPopup('error', 'Error', data.error_message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showPopup('error', 'Error', 'Failed to toggle plugin');
|
||||
});
|
||||
});
|
||||
|
||||
// Test button functionality
|
||||
testButton.addEventListener('click', function() {
|
||||
if (testButton.disabled) return;
|
||||
|
||||
testButton.disabled = true;
|
||||
testButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';
|
||||
|
||||
fetch('{% url "testPlugin:test_button" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 1) {
|
||||
testCountElement.textContent = data.test_count;
|
||||
showPopup(data.popup_message.type, data.popup_message.title, data.popup_message.message);
|
||||
} else {
|
||||
showPopup('error', 'Error', data.error_message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showPopup('error', 'Error', 'Failed to execute test');
|
||||
})
|
||||
.finally(() => {
|
||||
testButton.disabled = false;
|
||||
testButton.innerHTML = '<i class="fas fa-play"></i> Test Button';
|
||||
});
|
||||
});
|
||||
|
||||
// Popup message function
|
||||
function showPopup(type, title, message) {
|
||||
const popupContainer = document.getElementById('popup-container');
|
||||
const popup = document.createElement('div');
|
||||
popup.className = `popup-message ${type}`;
|
||||
|
||||
popup.innerHTML = `
|
||||
<button class="popup-close" onclick="this.parentElement.remove()">×</button>
|
||||
<div class="popup-title">${title}</div>
|
||||
<div class="popup-content">${message}</div>
|
||||
<div class="popup-time">${new Date().toLocaleTimeString()}</div>
|
||||
`;
|
||||
|
||||
popupContainer.appendChild(popup);
|
||||
|
||||
// Show popup
|
||||
setTimeout(() => popup.classList.add('show'), 100);
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
popup.classList.remove('show');
|
||||
setTimeout(() => popup.remove(), 300);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
286
testPlugin/templates/testPlugin/plugin_logs.html
Normal file
286
testPlugin/templates/testPlugin/plugin_logs.html
Normal file
@@ -0,0 +1,286 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans "Test Plugin Logs - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.logs-wrapper {
|
||||
background: transparent;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.logs-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.logs-header {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.logs-content {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.logs-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.logs-table th,
|
||||
.logs-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.logs-table th {
|
||||
background: var(--bg-secondary, #f8f9ff);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #2f3640);
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.logs-table td {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.log-action {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #2f3640);
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: var(--text-tertiary, #9ca3af);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.log-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.log-icon.info {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.log-icon.success {
|
||||
background: #e8f5e8;
|
||||
color: #388e3c;
|
||||
}
|
||||
|
||||
.log-icon.warning {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
.log-icon.error {
|
||||
background: #ffebee;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--text-secondary, #64748b);
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #2f3640);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
outline: none;
|
||||
border-color: #5856d6;
|
||||
box-shadow: 0 0 0 3px rgba(88,86,214,0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.logs-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.logs-table th,
|
||||
.logs-table td {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="logs-wrapper">
|
||||
<div class="logs-container">
|
||||
<!-- Logs Header -->
|
||||
<div class="logs-header">
|
||||
<h1>
|
||||
<i class="fas fa-list" style="margin-right: 12px; color: #5856d6;"></i>
|
||||
{% trans "Test Plugin Logs" %}
|
||||
</h1>
|
||||
<p>{% trans "View detailed activity logs for the test plugin" %}</p>
|
||||
</div>
|
||||
|
||||
<!-- Logs Content -->
|
||||
<div class="logs-content">
|
||||
<div class="filter-controls">
|
||||
<select class="filter-select" id="action-filter" title="{% trans 'Filter logs by action type' %}">
|
||||
<option value="">{% trans "All Actions" %}</option>
|
||||
<option value="test_button_click">{% trans "Test Button Clicks" %}</option>
|
||||
<option value="plugin_toggle">{% trans "Plugin Toggle" %}</option>
|
||||
<option value="settings_update">{% trans "Settings Update" %}</option>
|
||||
<option value="page_visit">{% trans "Page Visits" %}</option>
|
||||
</select>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_home' %}" class="btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
{% trans "Back to Plugin" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
|
||||
<i class="fas fa-book"></i>
|
||||
{% trans "Documentation" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if logs %}
|
||||
<table class="logs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Action" %}</th>
|
||||
<th>{% trans "Message" %}</th>
|
||||
<th>{% trans "Timestamp" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr class="log-row" data-action="{{ log.action }}">
|
||||
<td>
|
||||
<span class="log-icon {% if 'error' in log.action %}error{% elif 'success' in log.action or 'click' in log.action %}success{% elif 'warning' in log.action %}warning{% else %}info{% endif %}">
|
||||
{% if 'click' in log.action %}
|
||||
<i class="fas fa-mouse-pointer"></i>
|
||||
{% elif 'toggle' in log.action %}
|
||||
<i class="fas fa-toggle-on"></i>
|
||||
{% elif 'settings' in log.action %}
|
||||
<i class="fas fa-cog"></i>
|
||||
{% elif 'visit' in log.action %}
|
||||
<i class="fas fa-eye"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-info-circle"></i>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="log-action">{{ log.action|title|replace:"_":" " }}</span>
|
||||
</td>
|
||||
<td>{{ log.message }}</td>
|
||||
<td class="log-timestamp">{{ log.timestamp|date:"M d, Y H:i:s" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<h3>{% trans "No Logs Found" %}</h3>
|
||||
<p>{% trans "No activity logs available yet. Start using the plugin to see logs here." %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const actionFilter = document.getElementById('action-filter');
|
||||
const logRows = document.querySelectorAll('.log-row');
|
||||
|
||||
actionFilter.addEventListener('change', function() {
|
||||
const selectedAction = this.value;
|
||||
|
||||
logRows.forEach(row => {
|
||||
if (selectedAction === '' || row.dataset.action === selectedAction) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
259
testPlugin/templates/testPlugin/plugin_settings.html
Normal file
259
testPlugin/templates/testPlugin/plugin_settings.html
Normal file
@@ -0,0 +1,259 @@
|
||||
{% extends "baseTemplate/index.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans "Test Plugin Settings - CyberPanel" %}{% endblock %}
|
||||
|
||||
{% block header_scripts %}
|
||||
<style>
|
||||
.settings-wrapper {
|
||||
background: transparent;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
background: var(--bg-primary, white);
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: var(--shadow-md, 0 2px 8px rgba(0,0,0,0.08));
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #2f3640);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #5856d6;
|
||||
box-shadow: 0 0 0 3px rgba(88,86,214,0.1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #5856d6, #4a90e2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(88,86,214,0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="settings-wrapper">
|
||||
<div class="settings-container">
|
||||
<!-- Settings Header -->
|
||||
<div class="settings-header">
|
||||
<h1>
|
||||
<i class="fas fa-cog" style="margin-right: 12px; color: #5856d6;"></i>
|
||||
{% trans "Test Plugin Settings" %}
|
||||
</h1>
|
||||
<p>{% trans "Configure your test plugin settings and preferences" %}</p>
|
||||
</div>
|
||||
|
||||
<!-- Settings Form -->
|
||||
<div class="settings-form">
|
||||
<form id="settings-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="custom_message" class="form-label">
|
||||
{% trans "Custom Test Message" %}
|
||||
</label>
|
||||
<textarea
|
||||
id="custom_message"
|
||||
name="custom_message"
|
||||
class="form-control"
|
||||
rows="3"
|
||||
placeholder="Enter your custom message for the test button popup..."
|
||||
>{{ settings.custom_message }}</textarea>
|
||||
<small style="color: var(--text-secondary, #64748b); margin-top: 5px; display: block;">
|
||||
{% trans "This message will be displayed when you click the test button" %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{% trans "Plugin Status" %}
|
||||
</label>
|
||||
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; border: 1px solid var(--border-primary, #e8e9ff);">
|
||||
<strong style="color: {% if settings.plugin_enabled %}#10b981{% else %}#ef4444{% endif %};">
|
||||
{% if settings.plugin_enabled %}
|
||||
<i class="fas fa-check-circle"></i> {% trans "Enabled" %}
|
||||
{% else %}
|
||||
<i class="fas fa-times-circle"></i> {% trans "Disabled" %}
|
||||
{% endif %}
|
||||
</strong>
|
||||
<p style="margin: 8px 0 0 0; color: var(--text-secondary, #64748b); font-size: 14px;">
|
||||
{% trans "Use the toggle switch on the main page to enable/disable the plugin" %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
{% trans "Test Statistics" %}
|
||||
</label>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
|
||||
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; text-align: center;">
|
||||
<div style="font-size: 24px; font-weight: 700; color: #5856d6;">{{ settings.test_count }}</div>
|
||||
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">{% trans "Total Tests" %}</div>
|
||||
</div>
|
||||
<div style="padding: 12px; background: var(--bg-secondary, #f8f9ff); border-radius: 8px; text-align: center;">
|
||||
<div style="font-size: 24px; font-weight: 700; color: #5856d6;">{{ settings.last_test_time|date:"M d" }}</div>
|
||||
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">{% trans "Last Test" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border-primary, #e8e9ff);">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save"></i>
|
||||
{% trans "Save Settings" %}
|
||||
</button>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_home' %}" class="btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
{% trans "Back to Plugin" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'testPlugin:plugin_docs' %}" class="btn-secondary">
|
||||
<i class="fas fa-book"></i>
|
||||
{% trans "Documentation" %}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('settings-form');
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(form);
|
||||
const data = {
|
||||
custom_message: formData.get('custom_message')
|
||||
};
|
||||
|
||||
fetch('{% url "testPlugin:update_settings" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 1) {
|
||||
showNotification('success', 'Settings Updated', data.message);
|
||||
} else {
|
||||
showNotification('error', 'Error', data.error_message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showNotification('error', 'Error', 'Failed to update settings');
|
||||
});
|
||||
});
|
||||
|
||||
function showNotification(type, title, message) {
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.style.cssText = `
|
||||
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 ${type === 'success' ? '#10b981' : '#ef4444'};
|
||||
z-index: 9999;
|
||||
max-width: 400px;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
`;
|
||||
|
||||
notification.innerHTML = `
|
||||
<div style="font-weight: 600; color: var(--text-primary, #2f3640); margin-bottom: 4px;">${title}</div>
|
||||
<div style="font-size: 14px; color: var(--text-secondary, #64748b);">${message}</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Show notification
|
||||
setTimeout(() => notification.style.transform = 'translateX(0)', 100);
|
||||
|
||||
// Auto remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
17
testPlugin/urls.py
Normal file
17
testPlugin/urls.py
Normal file
@@ -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'),
|
||||
]
|
||||
246
testPlugin/views.py
Normal file
246
testPlugin/views.py
Normal file
@@ -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)})
|
||||
Reference in New Issue
Block a user