feat(ui): display licensed feature capabilities in Sub-CA & Licensing tab
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m19s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 1m19s
This commit is contained in:
+88
-11
@@ -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 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 & 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 & 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}
|
||||
|
||||
Reference in New Issue
Block a user