mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 13:15:31 +02:00
Raise default CYBERCP_MANAGED_APPS version cache TTL to 3600s to match Manage Applications inventory behavior and reduce cold DNF fetches. Refresh application_page_meta and synced manageServices static assets.
193 lines
7.7 KiB
Python
193 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Server-side metadata for Manage Applications page (version lists in HTML)."""
|
|
import json
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
from django.utils.translation import gettext as _
|
|
|
|
from .application_detection import detect_app_state, managed_apps_os_support
|
|
from .application_versions import get_available_versions, version_compare
|
|
|
|
_APP_IMAGES = {
|
|
'Elasticsearch': '/static/manageServices/images/elastic-search.png',
|
|
'Redis': '/static/manageServices/images/redis.png',
|
|
'RabbitMQ': '/static/manageServices/images/rabbitmq-logo.svg',
|
|
}
|
|
|
|
# Cache only repoquery/dnf-backed version lists (slow). Install state is always refreshed.
|
|
# Override with CYBERCP_MANAGED_APPS_VERSIONS_INVENTORY_TTL (seconds), default 3600 (1 hour).
|
|
_VERSIONS_INVENTORY_TTL_SECONDS = int(
|
|
os.environ.get('CYBERCP_MANAGED_APPS_VERSIONS_INVENTORY_TTL', '3600')
|
|
)
|
|
_VERSIONS_INVENTORY_CACHE = {}
|
|
_VERSIONS_INVENTORY_LOCK = threading.Lock()
|
|
|
|
|
|
def _versions_inventory_cache_get(cache_key):
|
|
now = time.time()
|
|
with _VERSIONS_INVENTORY_LOCK:
|
|
item = _VERSIONS_INVENTORY_CACHE.get(cache_key)
|
|
if not item:
|
|
return None
|
|
ts, inventory = item
|
|
if now - ts > _VERSIONS_INVENTORY_TTL_SECONDS:
|
|
try:
|
|
del _VERSIONS_INVENTORY_CACHE[cache_key]
|
|
except Exception:
|
|
pass
|
|
return None
|
|
return {k: list(v) for k, v in inventory.items()}
|
|
|
|
|
|
def _versions_inventory_cache_put(cache_key, inventory):
|
|
with _VERSIONS_INVENTORY_LOCK:
|
|
if len(_VERSIONS_INVENTORY_CACHE) > 16:
|
|
_VERSIONS_INVENTORY_CACHE.clear()
|
|
snap = {k: list(v) for k, v in (inventory or {}).items()}
|
|
_VERSIONS_INVENTORY_CACHE[cache_key] = (time.time(), snap)
|
|
|
|
|
|
def _cold_fetch_version_inventory(major, rmq, support):
|
|
"""Populate version lists from package managers (DNF/apt); can take many seconds."""
|
|
inv = {}
|
|
if not support.get('supported'):
|
|
for app_name in ('Elasticsearch', 'Redis', 'RabbitMQ'):
|
|
inv[app_name] = []
|
|
return inv
|
|
for app_name in ('Elasticsearch', 'Redis', 'RabbitMQ'):
|
|
try:
|
|
inv[app_name] = get_available_versions(app_name, major, rmq)
|
|
except BaseException:
|
|
inv[app_name] = []
|
|
return inv
|
|
|
|
|
|
def _resolve_version_inventory(cache_key, major, rmq, support):
|
|
cached = _versions_inventory_cache_get(cache_key)
|
|
if cached is not None:
|
|
return cached
|
|
inv = _cold_fetch_version_inventory(major, rmq, support)
|
|
_versions_inventory_cache_put(cache_key, inv)
|
|
return inv
|
|
|
|
|
|
def _assemble_manage_applications_payload(major, rmq, support, version_inv):
|
|
"""Build services + bootstrap JSON from fresh install state and cached (or new) version lists."""
|
|
services = []
|
|
bootstrap_apps = []
|
|
|
|
for app_name in ('Elasticsearch', 'Redis', 'RabbitMQ'):
|
|
state = detect_app_state(app_name)
|
|
services.append({
|
|
'image': _APP_IMAGES[app_name],
|
|
'name': app_name,
|
|
'installed': 'Installed' if state['installed'] else 'Not-Installed',
|
|
'installedVersion': state.get('installedVersion', ''),
|
|
})
|
|
|
|
versions = list(version_inv.get(app_name) or [])
|
|
latest_branch = ''
|
|
latest_global = ''
|
|
if versions:
|
|
latest_branch = versions[0]
|
|
latest_global = latest_branch
|
|
|
|
installed_version = state['installedVersion']
|
|
if installed_version and installed_version not in versions:
|
|
prepend_installed = True
|
|
if app_name == 'RabbitMQ':
|
|
from manageServices.application_rabbitmq_repo import (
|
|
filter_versions_for_stream,
|
|
)
|
|
prepend_installed = bool(
|
|
filter_versions_for_stream([installed_version], rmq)
|
|
)
|
|
if prepend_installed:
|
|
versions = [installed_version] + versions
|
|
|
|
ref_latest = latest_global or latest_branch
|
|
update_available = bool(
|
|
state['installed']
|
|
and installed_version
|
|
and ref_latest
|
|
and version_compare(installed_version, ref_latest) < 0
|
|
)
|
|
|
|
rabbitmq_versions_hint = ''
|
|
if app_name == 'RabbitMQ' and not versions:
|
|
if rmq == '4':
|
|
rabbitmq_versions_hint = _(
|
|
'Your OS is not unsupported: upstream RabbitMQ publishes 4.x RPMs suitable for '
|
|
'RHEL/Alma/Rocky 8 and 9 (RPM filenames may still contain el8; that is normal). '
|
|
'If this list stays empty, repository metadata may not expose 4.x to dnf yet—'
|
|
'refresh metadata (dnf makecache -y) or install the official .rpm from rabbitmq.com. '
|
|
'Check with: dnf repoquery rabbitmq-server --available --show-duplicates '
|
|
'(4.x lines look like rabbitmq-server-0:4.x.y-1.el8.noarch — search for :4., not a space after the colon).'
|
|
)
|
|
else:
|
|
rabbitmq_versions_hint = _(
|
|
'No 3.x builds were returned for this stream after refreshing Team RabbitMQ repos. '
|
|
'This is usually metadata or repo state—not OS support. Try: dnf makecache -y, '
|
|
'then dnf repoquery rabbitmq-server --available --show-duplicates.'
|
|
)
|
|
|
|
bootstrap_apps.append({
|
|
'name': app_name,
|
|
'installed': state['installed'],
|
|
'installedVersion': installed_version,
|
|
'latestAvailable': latest_branch,
|
|
'latestOverall': latest_global,
|
|
'updateAvailable': update_available,
|
|
'crossBranchUpdateSuggested': False,
|
|
'versions': versions,
|
|
'packageName': state['packageName'],
|
|
'adopted': bool(state['installed'] and not state['markerExists']),
|
|
'major': major if app_name == 'Elasticsearch' else '',
|
|
'rabbitmqStream': rmq if app_name == 'RabbitMQ' else '',
|
|
'rabbitmqVersionsHint': rabbitmq_versions_hint,
|
|
})
|
|
|
|
bootstrap = {'status': 1, 'apps': bootstrap_apps}
|
|
meta_json = json.dumps(bootstrap, ensure_ascii=False)
|
|
return services, meta_json
|
|
|
|
|
|
def build_manage_applications_page_data(es_major='8', rabbitmq_stream='4'):
|
|
"""
|
|
Build `services` for card HTML and a JSON-serializable bootstrap matching
|
|
/manageservices/applicationMeta shape (default ES major 8, RMQ stream 4).
|
|
|
|
Version lists are cached for _VERSIONS_INVENTORY_TTL_SECONDS to avoid repeated
|
|
DNF/repoquery on every page view; install status is always detected live.
|
|
"""
|
|
support = managed_apps_os_support()
|
|
major = str(es_major).strip() if str(es_major).strip() in ('7', '8', '9') else '8'
|
|
rmq = str(rabbitmq_stream).strip() if str(rabbitmq_stream).strip() in ('3', '4') else '4'
|
|
cache_key = 'major:{0}|rmq:{1}|support:{2}'.format(
|
|
major, rmq, 1 if support.get('supported') else 0
|
|
)
|
|
|
|
version_inv = _resolve_version_inventory(cache_key, major, rmq, support)
|
|
return _assemble_manage_applications_payload(major, rmq, support, version_inv)
|
|
|
|
|
|
def get_application_meta_response_dict(es_major='8', rabbitmq_stream='4'):
|
|
"""
|
|
JSON payload for POST /manageservices/applicationMeta.
|
|
Shares the same version-list inventory cache as the Manage Applications HTML bootstrap.
|
|
"""
|
|
support = managed_apps_os_support()
|
|
major = str(es_major).strip() if str(es_major).strip() in ('7', '8', '9') else '8'
|
|
rmq = str(rabbitmq_stream).strip() if str(rabbitmq_stream).strip() in ('3', '4') else '4'
|
|
|
|
_, meta_json = build_manage_applications_page_data(major, rmq)
|
|
payload = json.loads(meta_json)
|
|
return {
|
|
'status': 1,
|
|
'osSupportedForManagedApps': support['supported'],
|
|
'unsupportedReason': support['reason'],
|
|
'apps': payload.get('apps') or [],
|
|
}
|