05/22 Implement license version

This commit is contained in:
2026-05-22 16:31:19 -04:00
parent e6657e60cf
commit 687216e332
16 changed files with 1373 additions and 37 deletions
+151
View File
@@ -0,0 +1,151 @@
{% extends "base.html" %}
{% block title %}License{% endblock %}
{% block page_title %}License{% endblock %}
{% block content %}
<div class="row g-4">
{# ── Status card ─────────────────────────────────────────────────────────── #}
<div class="col-12 col-lg-5">
<div class="card h-100">
<div class="card-header"><i class="bi bi-key me-2"></i>License Status</div>
<div class="card-body">
{# Tier badge #}
{% if status.tier == 'enterprise' %}
<span class="badge bg-purple fs-6 mb-3" style="background:#6d28d9!important;">
<i class="bi bi-award me-1"></i>Enterprise
</span>
{% elif status.tier == 'business' %}
<span class="badge bg-primary fs-6 mb-3">
<i class="bi bi-briefcase me-1"></i>Business
</span>
{% else %}
<span class="badge bg-secondary fs-6 mb-3">
<i class="bi bi-person me-1"></i>Community
</span>
{% endif %}
{% if status.valid %}
<p class="mb-1"><i class="bi bi-check-circle-fill text-success me-2"></i>
<strong>License is active</strong></p>
{% if status.customer %}
<p class="mb-1 text-muted small">
<i class="bi bi-building me-1"></i>{{ status.customer }}
</p>
{% endif %}
{% if status.email %}
<p class="mb-1 text-muted small">
<i class="bi bi-envelope me-1"></i>{{ status.email }}
</p>
{% endif %}
{% if status.issued_at %}
<p class="mb-1 text-muted small">
<i class="bi bi-calendar-check me-1"></i>Issued: {{ status.issued_at }}
</p>
{% endif %}
{% if status.expires_at %}
<p class="mb-1 text-muted small">
<i class="bi bi-calendar-x me-1"></i>Expires: {{ status.expires_at }}
{% if days_left is not none %}
{% if days_left < 30 %}
<span class="badge bg-warning text-dark ms-1">{{ days_left }}d left</span>
{% else %}
<span class="badge bg-success ms-1">{{ days_left }}d left</span>
{% endif %}
{% endif %}
</p>
{% endif %}
{% else %}
<p class="mb-2">
<i class="bi bi-x-circle-fill text-danger me-2"></i>
<strong>No active license</strong>
</p>
<p class="text-muted small mb-0">
{% if status.get('reason') == 'expired' %}
Your license expired on <strong>{{ status.expires_at }}</strong>.
Contact your vendor to renew.
{% elif status.get('reason') == 'no_key' %}
No license key is configured.
Add <code>LICENSE_KEY=TDESK-...</code> to your <code>.env</code> file and restart.
{% elif status.get('reason') == 'no_public_key' %}
The application's public key has not been configured yet.
Contact your system administrator.
{% else %}
The license key is invalid or has been tampered with.
Contact your vendor for a replacement key.
{% endif %}
</p>
{% endif %}
</div>
</div>
</div>
{# ── Feature checklist ───────────────────────────────────────────────────── #}
<div class="col-12 col-lg-7">
<div class="card h-100">
<div class="card-header"><i class="bi bi-grid-3x3-gap me-2"></i>Feature Access</div>
<div class="card-body">
<table class="table table-sm mb-0">
<thead>
<tr>
<th>Feature</th>
<th class="text-center">Community</th>
<th class="text-center">Business</th>
<th class="text-center">Enterprise</th>
</tr>
</thead>
<tbody>
{% set rows = [
('Core ticketing, comments, attachments', True, True, True),
('Knowledge base', True, True, True),
('User management, bulk actions, CSV export', True, True, True),
('AI Chatbot (Groq)', False, True, True),
('SLA tracking &amp; breach notifications', False, True, True),
('Weekly IT digest email', False, True, True),
('Ticket watchers', False, True, True),
('Time tracking', False, True, True),
('Satisfaction surveys', False, True, True),
('Email ingestion (IMAP)', False, False, True),
] %}
{% for label, com, biz, ent in rows %}
<tr>
<td style="font-size:.9rem;">{{ label | safe }}</td>
{% for flag, tier_name in [(com, 'community'), (biz, 'business'), (ent, 'enterprise')] %}
<td class="text-center">
{% set is_current = (status.tier == tier_name) %}
{% if flag %}
<i class="bi bi-check-circle-fill {% if is_current %}text-success{% else %}text-muted{% endif %}"></i>
{% else %}
<i class="bi bi-dash-circle text-muted opacity-25"></i>
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{# ── Setup instructions ──────────────────────────────────────────────────── #}
<div class="col-12">
<div class="card">
<div class="card-header"><i class="bi bi-info-circle me-2"></i>How to Apply a License Key</div>
<div class="card-body">
<ol class="mb-0" style="font-size:.9rem;line-height:2;">
<li>Obtain a license key from your vendor (format: <code>TDESK-...</code>).</li>
<li>Open the <code>.env</code> file in your TechDesk installation directory.</li>
<li>Add or update the line: <code>LICENSE_KEY=TDESK-your-key-here</code></li>
<li>Restart the TechDesk service: <code>sudo systemctl restart gunicorn</code></li>
<li>Return to this page to confirm the license is active.</li>
</ol>
</div>
</div>
</div>
</div>
{% endblock %}
+33 -1
View File
@@ -1162,6 +1162,10 @@
<li><a href="{{ url_for('admin.settings') }}" class="{{ 'active' if request.endpoint == 'admin.settings' }}">
<i class="bi bi-gear"></i> Settings
</a></li>
<li><a href="{{ url_for('admin.license_page') }}" class="{{ 'active' if request.endpoint == 'admin.license_page' }}">
<i class="bi bi-key"></i> License
{% if not license.valid %}<span class="badge bg-warning text-dark ms-1" style="font-size:.65rem;">!</span>{% endif %}
</a></li>
{% endif %}
</ul>
{% endif %}
@@ -1232,6 +1236,32 @@
</div>
{% endfor %}
{% endwith %}
{% if current_user.is_authenticated and current_user.is_admin %}
{% if not license.valid %}
<div class="alert alert-warning alert-dismissible mb-3" role="alert">
<i class="bi bi-key me-2"></i>
{% if license.get('reason') == 'expired' %}
Your TechDesk license expired on <strong>{{ license.expires_at }}</strong>.
Features are restricted to Community tier.
{% elif license.get('reason') == 'no_key' %}
No license key configured. TechDesk is running in <strong>Community mode</strong>.
{% else %}
License key is invalid. TechDesk is running in <strong>Community mode</strong>.
{% endif %}
<a href="{{ url_for('admin.license_page') }}" class="alert-link ms-1">View license details →</a>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% elif license.days_left is not none and license.days_left < 30 %}
<div class="alert alert-info alert-dismissible mb-3" role="alert">
<i class="bi bi-key me-2"></i>
Your TechDesk license expires in <strong>{{ license.days_left }} day{{ 's' if license.days_left != 1 }}</strong>.
<a href="{{ url_for('admin.license_page') }}" class="alert-link ms-1">View license →</a>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
{% endif %}
{% block content %}{% endblock %}
</div>
</div>
@@ -1251,7 +1281,8 @@
</div>
</div>
<!-- ── Chat FAB ── -->
<!-- ── Chat FAB (Business+ license required) ── -->
{% if license.tier in ('business', 'enterprise') %}
<div id="chat-fab" onclick="toggleChat()" title="IT Assistant">
<i class="bi bi-robot"></i>
</div>
@@ -1276,6 +1307,7 @@
<button class="chat-send" onclick="sendChat()"><i class="bi bi-send-fill"></i></button>
</div>
</div>
{% endif %}
{% else %}
<!-- Not authenticated — minimal layout -->
+5 -3
View File
@@ -828,7 +828,8 @@ function buildCommentEl(c) {
</div>
</div>
<!-- Watch / Unwatch -->
<!-- Watch / Unwatch (Business+ license required) -->
{% if license.tier in ('business', 'enterprise') %}
<div class="card mb-3">
<div class="card-body py-2 px-3 d-flex align-items-center justify-content-between">
<span style="font-size:13px;color:var(--muted);">
@@ -843,9 +844,10 @@ function buildCommentEl(c) {
</button>
</div>
</div>
{% endif %}
<!-- Time Tracking (IT staff only) -->
{% if current_user.is_it_staff %}
<!-- Time Tracking (IT staff only, Business+ license required) -->
{% if current_user.is_it_staff and license.tier in ('business', 'enterprise') %}
<div class="card mb-3">
<div class="card-header d-flex align-items-center justify-content-between">
<span><i class="bi bi-clock me-2"></i>Time Logged</span>