From e32219edde429ec307b178a04e4b66be7b4269d5 Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 2 Feb 2026 02:18:05 +0100 Subject: [PATCH] Plugin Store: badges (NEW/Stable/Unstable/STALE), Activate/Deactivate All, categories & premium docs in help --- baseTemplate/views.py | 35 +- pluginHolder/templates/pluginHolder/help.html | 94 +++++- .../templates/pluginHolder/plugins.html | 299 ++++++++++++++++-- pluginHolder/views.py | 37 ++- 4 files changed, 411 insertions(+), 54 deletions(-) diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 1c7d05433..172e86731 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -129,6 +129,11 @@ def getAdminStatus(request): def getSystemStatus(request): + default_fallback = { + 'cpuUsage': 0, 'ramUsage': 0, 'diskUsage': 0, + 'cpuCores': 2, 'ramTotalMB': 4096, 'diskTotalGB': 100, + 'diskFreeGB': 100, 'uptime': 'N/A' + } try: val = request.session['userID'] currentACL = ACLManager.loadedACL(val) @@ -215,31 +220,13 @@ def getSystemStatus(request): except KeyError as e: logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] KeyError - No session userID: {str(e)}') - # Return default values on error - default_data = { - 'cpuUsage': 0, - 'ramUsage': 0, - 'diskUsage': 0, - 'cpuCores': 2, - 'ramTotalMB': 4096, - 'diskTotalGB': 100, - 'diskFreeGB': 100, - 'uptime': 'N/A' - } - return HttpResponse(json.dumps(default_data)) + return HttpResponse(json.dumps(default_fallback)) except Exception as e: - # Return default values on error - default_data = { - 'cpuUsage': 0, - 'ramUsage': 0, - 'diskUsage': 0, - 'cpuCores': 2, - 'ramTotalMB': 4096, - 'diskTotalGB': 100, - 'diskFreeGB': 100, - 'uptime': 'N/A' - } - return HttpResponse(json.dumps(default_data)) + logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] Exception: {str(e)}') + try: + return HttpResponse(json.dumps(default_fallback)) + except Exception: + return HttpResponse('{"cpuUsage":0,"ramUsage":0,"diskUsage":0,"cpuCores":2,"ramTotalMB":4096,"diskTotalGB":100,"diskFreeGB":100,"uptime":"N/A"}', content_type='application/json') def getLoadAverage(request): diff --git a/pluginHolder/templates/pluginHolder/help.html b/pluginHolder/templates/pluginHolder/help.html index 66bdcbdea..da5e53b13 100644 --- a/pluginHolder/templates/pluginHolder/help.html +++ b/pluginHolder/templates/pluginHolder/help.html @@ -323,6 +323,9 @@
  • {% trans "Creating Your First Plugin" %}
  • {% trans "Plugin Structure & Files" %}
  • {% trans "Version Numbering (Semantic Versioning)" %}
  • +
  • {% trans "Plugin Categories" %}
  • +
  • {% trans "Freshness Badges" %}
  • +
  • {% trans "Premium/Paid Plugin Creation" %}
  • {% trans "Core Components" %}
  • {% trans "Advanced Features" %}
  • {% trans "Best Practices" %}
  • @@ -402,7 +405,7 @@ mkdir -p migrations </cyberpanelPluginConfig>
    - {% trans "Required: Category (type)" %}: {% trans "The <type> field is required. Valid categories: Utility, Security, Backup, Performance. Plugins without a valid category will not appear in the Plugin Store." %} + {% trans "Required: Category (type)" %}: {% trans "The <type> field is required. See the Plugin Categories section below for valid options. Plugins without a valid category will not appear in the Plugin Store." %}

    {% trans "Step 3: Create urls.py" %}

    @@ -510,6 +513,93 @@ def main_view(request):
    <version>1.0.0</version>

    {% trans "Never use formats like '1.0' or 'v1.0'. Always use the full semantic version: '1.0.0'" %}

    +

    {% trans "Plugin Categories" %}

    +

    {% trans "The <type> field in meta.xml determines how your plugin is grouped in the Plugin Store. Use exactly one of these values (case-sensitive):" %}

    + + + + + + + + + + + + + + + + + + +
    {% trans "Category" %}{% trans "Purpose" %}
    Utility{% trans "General-purpose tools, helpers, and utilities" %}
    Security{% trans "Security features: firewalls, fail2ban, SSL, etc." %}
    Backup{% trans "Backup, snapshot, and restore functionality" %}
    Performance{% trans "Caching, optimization, and performance tuning" %}
    Monitoring{% trans "Monitoring, alerts, and health checks" %}
    Integration{% trans "Third-party integrations: Discord, Slack, webhooks, APIs" %}
    Email{% trans "Email marketing, SMTP, mail management" %}
    Development{% trans "Developer tools: PM2, Node.js, deployment" %}
    Analytics{% trans "Analytics, tracking, and reporting" %}
    + +

    {% trans "Freshness Badges" %}

    +

    {% trans "The Plugin Store and Installed Plugins views display freshness badges based on the last update date (modify_date from GitHub commit or meta.xml file mtime). These help users quickly see how actively maintained a plugin is:" %}

    + + + + + + + + + + + + + + +
    {% trans "Badge" %}{% trans "Condition" %}{% trans "Meaning" %}
    NEW{% trans "Updated within last 90 days" %}{% trans "Recently released or actively maintained" %}
    Stable{% trans "Updated within last 365 days" %}{% trans "Updated within the past year" %}
    Unstable{% trans "1–2 years since last update" %}{% trans "May need maintenance; consider forking or updating" %}
    STALE{% trans "Over 2 years since last update" %}{% trans "Not updated recently; use with caution" %}
    +

    {% trans "Badges are calculated automatically from the plugin's modify_date. For plugins in the Plugin Store, this comes from the GitHub repository's last commit. For installed plugins, it uses the meta.xml file modification time." %}

    + +

    {% trans "Premium/Paid Plugin Creation" %}

    +

    {% trans "You can create premium (paid) plugins and implement your own verification system. This includes optional encryption between the plugin and your verification site to prevent unauthorized bypass." %}

    + +

    {% trans "1. Mark Your Plugin as Paid in meta.xml" %}

    +
    <paid>true</paid>
    +<patreon_tier>Your Tier Name</patreon_tier>
    +<patreon_url>https://www.patreon.com/your-page</patreon_url>
    +

    {% trans "Set <paid>true</paid> to display the Premium badge and subscription prompts in the Plugin Store." %}

    + +

    {% trans "2. Build Your Own Verification System" %}

    +

    {% trans "Premium plugins typically verify access via a remote API. You can host this on your own site. Common verification methods:" %}

    + + +

    {% trans "3. Optional: Encrypt Plugin–API Communication" %}

    +

    {% trans "To protect against users modifying your plugin to bypass verification, you can encrypt the communication between the plugin and your verification API using AES-256-CBC. This ensures:" %}

    + +

    {% trans "Implementation outline:" %}

    +
      +
    1. {% trans "Generate a 32-byte secret key and store it in your API config (e.g. config.php)" %}
    2. +
    3. {% trans "In your plugin (Python), encrypt outgoing requests and decrypt responses using the same key" %}
    4. +
    5. {% trans "On your API (PHP/Python), decrypt incoming requests and encrypt responses" %}
    6. +
    7. {% trans "Use the X-Encrypted: 1 header to indicate encrypted payloads" %}
    8. +
    +

    {% trans "Both sides must use the same AES-256-CBC key. Keep the key secret and never commit it to public repositories. Store it in a protected config file outside the web root or in environment variables." %}

    + +

    {% trans "4. Typical Premium Plugin Flow" %}

    +
      +
    1. {% trans "User installs your premium plugin" %}
    2. +
    3. {% trans "Plugin shows an activation screen (Patreon/PayPal links, or activation key input)" %}
    4. +
    5. {% trans "Plugin calls your verification API with user identifier (email, domain, IP) or activation key" %}
    6. +
    7. {% trans "Your API checks: Plugin Grants, activation key, Patreon, or PayPal — in your preferred order" %}
    8. +
    9. {% trans "If access is granted, API returns success; plugin unlocks features and optionally stores the key locally" %}
    10. +
    +
    + {% trans "Tip" %}: {% trans "Provide multiple verification paths: Patreon for subscribers, PayPal for one-time purchasers, Plugin Grants for beta testers or sponsors, and activation keys for manual grants. Encryption is optional but recommended for paid plugins to deter bypass attempts." %} +
    +

    {% trans "Core Components" %}

    {% trans "1. Authentication & Security" %}

    {% trans "Always use the cyberpanel_login_required decorator:" %}

    @@ -672,7 +762,7 @@ zip -r myPlugin-v1.0.0.zip . \

    {% trans "Author" %}: master3395 | {% trans "Version" %}: 2.0.0 | - {% trans "Last Updated" %}: 2026-01-04 + {% trans "Last Updated" %}: 2026-02-02

    diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index 49d9c7dc4..389d4a685 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -200,6 +200,32 @@ border: 1px solid #c3e6cb; } + .freshness-badge-new, .freshness-badge-stable, .freshness-badge-stale { + display: inline-block; + padding: 3px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + margin-top: 4px; + } + .freshness-badge-new { + background: #fef08a; + color: #854d0e; + } + .freshness-badge-stable { + background: #bbf7d0; + color: #166534; + } + .freshness-badge-stale { + background: #fecaca; + color: #991b1b; + } + .freshness-badge-unstable { + background: #e5e7eb; + color: #4b5563; + } + .plugin-pricing-badge.paid { background: #fff3cd; color: #856404; @@ -443,11 +469,62 @@ margin: 0 auto; } - /* View Toggle */ + /* View Toggle and Bulk Actions */ + .view-toggle-wrapper { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 16px; + margin-bottom: 20px; + } .view-toggle { display: flex; gap: 10px; - margin-bottom: 20px; + } + .bulk-actions-header { + display: flex !important; + visibility: visible !important; + opacity: 1 !important; + } + .bulk-actions-header .btn-bulk { + margin: 0; + flex-shrink: 0; + } + .btn-bulk { + padding: 8px 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid transparent; + display: inline-flex; + align-items: center; + gap: 6px; + } + .btn-activate-all { + background: #28a745; + color: white; + border-color: #218838; + } + .btn-activate-all:hover:not(:disabled) { + background: #218838; + } + .btn-activate-all:disabled { + opacity: 0.6; + cursor: not-allowed; + } + .btn-deactivate-all { + background: #ffc107; + color: #212529; + border-color: #e0a800; + } + .btn-deactivate-all:hover:not(:disabled) { + background: #e0a800; + } + .btn-deactivate-all:disabled { + opacity: 0.6; + cursor: not-allowed; } .view-btn { @@ -1076,7 +1153,7 @@