Add better plugin description + new example plugin

Add better plugin description + new example plugin
This commit is contained in:
Master3395
2025-09-11 20:04:09 +02:00
parent 41ead838ef
commit dfbbccf073
15 changed files with 3801 additions and 0 deletions

View 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;
}

View 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()">&times;</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;