feat(ui): display licensed feature capabilities in Sub-CA & Licensing tab
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m19s

This commit is contained in:
2026-09-30 17:48:01 +02:00
parent 2b0b680188
commit 2d33b14d60
+89 -12
View File
@@ -1188,11 +1188,13 @@ def export_license_request_lic(config: Dict[str, Any]) -> Tuple[Dict[str, Any],
)
def is_feature_licensed(feature: str, db_path: Optional[str] = None) -> bool:
def get_active_licensed_features(db_path: Optional[str] = None) -> List[str]:
"""
Checks whether a specific enterprise capability (e.g. 'enterprise_db') is unlocked
either via custom X.509 certificate extensions or the persistent license configuration.
Returns the list of all active enterprise capability identifiers
authorized either via X.509 certificate extensions or the persistent database license config.
"""
features = set()
# 1. Check in-memory / active Subordinate CA certificate
ca_pem = SERVER_STATE.get("ca_cert_pem")
if not ca_pem:
@@ -1208,9 +1210,9 @@ def is_feature_licensed(feature: str, db_path: Optional[str] = None) -> bool:
if ca_pem:
try:
info = enrollment.extract_license_info_from_cert(ca_pem)
cert_features = info.get("features", [])
if feature in cert_features:
return True
for feat in info.get("features", []):
if feat:
features.add(str(feat).strip())
except Exception:
pass
@@ -1230,14 +1232,24 @@ def is_feature_licensed(feature: str, db_path: Optional[str] = None) -> bool:
feat_list = json.loads(feat_raw)
else:
feat_list = [f.strip() for f in feat_raw.split(",") if f.strip()]
if feature in feat_list:
return True
elif isinstance(feat_raw, list) and feature in feat_raw:
return True
for f_item in feat_list:
if f_item:
features.add(str(f_item).strip())
elif isinstance(feat_raw, list):
for f_item in feat_raw:
if f_item:
features.add(str(f_item).strip())
except Exception:
pass
return False
return sorted(list(features))
def is_feature_licensed(feature: str, db_path: Optional[str] = None) -> bool:
"""Checks whether a specific enterprise capability (e.g. 'enterprise_db') is authorized."""
if not feature:
return False
return feature in get_active_licensed_features(db_path)
def get_server_public_key() -> Optional[pgpy.PGPKey]:
@@ -2863,7 +2875,8 @@ def get_license_summary() -> Dict[str, Any]:
"days_remaining": info.get("days_remaining", 0),
"fingerprint": info.get("fingerprint", "N/A"),
"csr_exists": csr_exists,
"server_fingerprint": config.get("server_fingerprint", "N/A")
"server_fingerprint": config.get("server_fingerprint", "N/A"),
"features": get_active_licensed_features(db_path)
}
@@ -3396,6 +3409,55 @@ def serve_webui(request: Request = None):
pg_disabled_attr = "" if is_db_unlocked else "disabled"
pg_label_style = "" if is_db_unlocked else "opacity: 0.55; cursor: not-allowed;"
active_features = summary.get("features", [])
feature_cards = []
if active_features:
for feat in active_features:
if feat == "enterprise_db":
feature_cards.append("""
<div style="background: #0f172a; padding: 14px 18px; border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.35); display: flex; align-items: flex-start; gap: 14px;">
<div style="font-size: 22px; line-height: 1;">🐘</div>
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 3px;">
<strong style="color: #34d399; font-size: 14px;">enterprise_db</strong>
<span class="badge badge-active" style="font-size: 10px;">AUTHORIZED</span>
</div>
<div style="font-size: 12px; color: var(--text-muted); line-height: 1.4;">
<strong>Enterprise Database &amp; High-Throughput Engine:</strong> Unlocks external PostgreSQL storage clustering, customizable in-memory RAM buffering with batch flushing, and OpenPGP zero-knowledge data-at-rest encryption.
</div>
</div>
</div>
""")
else:
esc_feat = html.escape(str(feat))
feat_title = html.escape(str(feat).replace("_", " ").title())
feature_cards.append(f"""
<div style="background: #0f172a; padding: 14px 18px; border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.35); display: flex; align-items: flex-start; gap: 14px;">
<div style="font-size: 22px; line-height: 1;">✨</div>
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 3px;">
<strong style="color: #34d399; font-size: 14px;">{esc_feat}</strong>
<span class="badge badge-active" style="font-size: 10px;">AUTHORIZED</span>
</div>
<div style="font-size: 12px; color: var(--text-muted); line-height: 1.4;">
<strong>{feat_title}:</strong> Advanced capability authorized by your license certificate.
</div>
</div>
</div>
""")
else:
feature_cards.append("""
<div style="background: #0f172a; padding: 16px 20px; border-radius: 8px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;">
<div>
<div style="font-weight: 700; color: #94a3b8; font-size: 14px; margin-bottom: 2px;">No Enterprise Capabilities Active</div>
<div style="font-size: 12px; color: var(--text-muted);">Standard Community Edition is running. External PostgreSQL pooling, RAM queue buffering, and zero-knowledge encryption require an <code>enterprise_db</code> capability package.</div>
</div>
<button onclick="openImportLicModal()" class="btn btn-accent" style="padding: 6px 12px; font-size: 11px;">📥 Import Enterprise .lic</button>
</div>
""")
features_block_html = "".join(feature_cards)
ent_community_notice = "" if is_db_unlocked else f"""
<div style="margin-bottom: 20px; background: rgba(59, 130, 246, 0.08); border: 1px solid rgba(59, 130, 246, 0.25); border-radius: 8px; padding: 12px 16px; font-size: 13px; color: #94a3b8; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px;">
<div>
@@ -3998,6 +4060,21 @@ def serve_webui(request: Request = None):
</div>
</div>
</div>
<!-- Licensed Capabilities & Feature Modules -->
<div class="card" style="margin-top: 20px;">
<div class="card-header">
<div>
<span class="card-title">✨ Licensed Capabilities &amp; Feature Modules</span>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 2px;">Features authorized by your subordinate CA license package and upstream authority</div>
</div>
<span class="badge {'badge-active' if active_features else 'badge-os'}">{len(active_features)} ACTIVE FEATURES</span>
</div>
<div style="display: flex; flex-direction: column; gap: 10px; margin-top: 6px;">
{features_block_html}
</div>
</div>
</div>
{db_tab_pane_html}