diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html
index 771a0c947..0476e85f7 100644
--- a/baseTemplate/templates/baseTemplate/index.html
+++ b/baseTemplate/templates/baseTemplate/index.html
@@ -2504,7 +2504,7 @@
-
+
diff --git a/manageServices/application_detection.py b/manageServices/application_detection.py
index 7054d7248..36162f04e 100644
--- a/manageServices/application_detection.py
+++ b/manageServices/application_detection.py
@@ -43,6 +43,55 @@ def is_debian_family():
return os.path.exists('/etc/debian_version') or os.path.exists('/etc/lsb-release')
+def rhel_major_from_os_release():
+ """
+ RHEL-family OS major version (8, 9, 10, …) from /etc/os-release (or redhat-release).
+ Returns None for Debian/Ubuntu or if the OS cannot be classified as RHEL-like.
+ Used to align Packagecloud Yum baseurls (el/8 vs el/9) with the running system.
+ """
+ if is_debian_family():
+ return None
+ os_release = '/etc/os-release'
+ version_id = None
+ platform_id = None
+ if os.path.exists(os_release):
+ try:
+ with open(os_release, 'r', encoding='utf-8', errors='replace') as fh:
+ for line in fh:
+ line = line.strip()
+ if line.startswith('VERSION_ID='):
+ version_id = line.split('=', 1)[1].strip().strip('"').strip("'")
+ elif line.startswith('PLATFORM_ID='):
+ platform_id = line.split('=', 1)[1].strip().strip('"').strip("'")
+ except Exception:
+ pass
+ if version_id:
+ match = re.match(r'^(\d+)', version_id)
+ if match:
+ major = int(match.group(1))
+ if 6 <= major <= 15:
+ return major
+ if platform_id:
+ match = re.search(r'el(\d+)', platform_id, re.IGNORECASE)
+ if match:
+ major = int(match.group(1))
+ if 6 <= major <= 15:
+ return major
+ redhat_release = '/etc/redhat-release'
+ if os.path.exists(redhat_release):
+ try:
+ with open(redhat_release, 'r', encoding='utf-8', errors='replace') as fh:
+ txt = fh.read()
+ match = re.search(r'release\s+(\d+)', txt, re.IGNORECASE)
+ if match:
+ major = int(match.group(1))
+ if 6 <= major <= 15:
+ return major
+ except Exception:
+ pass
+ return None
+
+
def is_centos7():
release_paths = ['/etc/centos-release', '/etc/redhat-release', '/etc/os-release']
text_blob = ''
diff --git a/manageServices/application_page_meta.py b/manageServices/application_page_meta.py
index e949f827d..095705466 100644
--- a/manageServices/application_page_meta.py
+++ b/manageServices/application_page_meta.py
@@ -4,6 +4,8 @@ import json
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
@@ -46,16 +48,16 @@ def _page_meta_cache_put(cache_key, services, meta_json):
)
-def build_manage_applications_page_data(es_major='8', rabbitmq_stream='3'):
+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 3).
+ /manageservices/applicationMeta shape (default ES major 8, RMQ stream 4).
"""
services = []
bootstrap_apps = []
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 '3'
+ 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
)
@@ -87,7 +89,16 @@ def build_manage_applications_page_data(es_major='8', rabbitmq_stream='3'):
installed_version = state['installedVersion']
if installed_version and installed_version not in versions:
- versions = [installed_version] + 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(
@@ -97,6 +108,24 @@ def build_manage_applications_page_data(es_major='8', rabbitmq_stream='3'):
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'],
@@ -110,6 +139,7 @@ def build_manage_applications_page_data(es_major='8', rabbitmq_stream='3'):
'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}
@@ -118,7 +148,7 @@ def build_manage_applications_page_data(es_major='8', rabbitmq_stream='3'):
return services, meta_json
-def get_application_meta_response_dict(es_major='8', rabbitmq_stream='3'):
+def get_application_meta_response_dict(es_major='8', rabbitmq_stream='4'):
"""
JSON payload for POST /manageservices/applicationMeta.
Reuses the same TTL cache as the Manage Applications HTML bootstrap so
@@ -126,7 +156,7 @@ def get_application_meta_response_dict(es_major='8', rabbitmq_stream='3'):
"""
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 '3'
+ 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
)
diff --git a/manageServices/application_rabbitmq_repo.py b/manageServices/application_rabbitmq_repo.py
index 7f7928713..11c623d5e 100644
--- a/manageServices/application_rabbitmq_repo.py
+++ b/manageServices/application_rabbitmq_repo.py
@@ -3,10 +3,13 @@
Team RabbitMQ package repositories (Packagecloud) and Erlang compatibility
for RabbitMQ 3.x vs 4.x installation streams.
"""
+import os
import re
import subprocess
+import tempfile
+import time
-from manageServices.application_detection import is_debian_family
+from manageServices.application_detection import is_debian_family, rhel_major_from_os_release
# Official Packagecloud install scripts (RabbitMQ team).
_RPM_ERLANG_SCRIPT = (
@@ -26,6 +29,42 @@ _DEB_SERVER_SCRIPT = (
_MIN_OTP_STREAM_3 = 25
_MIN_OTP_STREAM_4 = 26
+# When Packagecloud metadata lists 3.x but no 4.x (common on el/9 trees), still offer GA
+# releases from https://www.rabbitmq.com/release-information so the panel can run
+# dnf install rabbitmq-server- (RPMs are often el8-tagged on EL9 per upstream docs).
+# Update this tuple when new 4.x patches ship.
+RABBITMQ_4X_METADATA_FALLBACK_VERSIONS = (
+ '4.2.5',
+ '4.2.4',
+ '4.2.3',
+ '4.2.2',
+ '4.2.1',
+ '4.2.0',
+ '4.1.8',
+ '4.1.7',
+ '4.1.6',
+ '4.1.5',
+ '4.1.4',
+ '4.1.3',
+ '4.1.2',
+ '4.1.1',
+ '4.1.0',
+ '4.0.9',
+ '4.0.8',
+ '4.0.7',
+ '4.0.6',
+ '4.0.5',
+ '4.0.4',
+ '4.0.3',
+ '4.0.2',
+ '4.0.1',
+ '4.0.0',
+)
+
+_YUM_REPOS_D = '/etc/yum.repos.d'
+# Packagecloud RabbitMQ repos use .../el/N/... in baseurl; must match host RHEL major.
+_EL_URL_SEGMENT = re.compile(r'(/el/)(\d+)(/)')
+
def _run(cmd, timeout=300):
try:
@@ -48,7 +87,7 @@ def _run_shell_trusted(script_url, timeout=300):
def normalize_rabbitmq_stream(value):
- s = str(value or '3').strip()
+ s = str(value or '4').strip()
if s in ('4', '4.x', '41', '4.1'):
return '4'
return '3'
@@ -64,6 +103,155 @@ def _write_status(status_file, message):
pass
+def _rhel_refresh_package_metadata(status_file=None, aggressive=False):
+ """
+ Refresh DNF/YUM metadata after adding Packagecloud repos.
+ Retries on failure. When aggressive (e.g. 4.x stream), expire cache first
+ so new rabbitmq-server builds become visible.
+ """
+ if is_debian_family():
+ return True
+ if aggressive:
+ exp_rc, _, exp_err = _run(['dnf', 'clean', 'expire-cache'], timeout=90)
+ if exp_rc != 0:
+ _write_status(
+ status_file,
+ 'dnf expire-cache (non-fatal): ' + (exp_err or '')[:120]
+ )
+ last_err = ''
+ for attempt in range(1, 4):
+ for cache_cmd in (['dnf', 'makecache', '-y'], ['yum', 'makecache', '-y']):
+ c_rc, c_out, c_err = _run(cache_cmd, timeout=180)
+ if c_rc == 0:
+ _write_status(
+ status_file,
+ 'RPM metadata refreshed ({0}, attempt {1}).'.format(
+ cache_cmd[0], attempt
+ )
+ )
+ return True
+ last_err = (c_err or c_out or str(c_rc)).strip()
+ time.sleep(min(3 * attempt, 15))
+ _write_status(
+ status_file,
+ 'RPM metadata refresh failed after retries: ' + (last_err or 'unknown')[:240]
+ )
+ return False
+
+
+def refresh_rhel_metadata_for_rabbitmq_repos(status_file=None):
+ """
+ Public: force another metadata refresh (e.g. when repoquery finds no 4.x RPMs).
+ """
+ return _rhel_refresh_package_metadata(status_file=status_file, aggressive=True)
+
+
+def align_rabbitmq_packagecloud_repos_to_os(status_file=None):
+ """
+ If Team RabbitMQ Packagecloud .repo files point at /el/M/ but this host is el/N,
+ rewrite URLs to /el/N/ (e.g. stale el/8 on AlmaLinux 9). Only touches files that
+ mention both packagecloud.io and rabbitmq. Requires root to write /etc/yum.repos.d.
+ """
+ if is_debian_family():
+ return
+ target_major = rhel_major_from_os_release()
+ if target_major is None:
+ return
+ if not os.path.isdir(_YUM_REPOS_D):
+ return
+ try:
+ repo_names = sorted(
+ n for n in os.listdir(_YUM_REPOS_D) if n.endswith('.repo')
+ )
+ except OSError as err:
+ _write_status(
+ status_file,
+ 'rabbitmq repo align: cannot list {0}: {1}'.format(
+ _YUM_REPOS_D, str(err)[:100]
+ )
+ )
+ return
+
+ for repo_name in repo_names:
+ repo_path = os.path.join(_YUM_REPOS_D, repo_name)
+ try:
+ with open(repo_path, 'r', encoding='utf-8', errors='replace') as handle:
+ original = handle.read()
+ except OSError:
+ continue
+ lower = original.lower()
+ if 'packagecloud.io' not in lower or 'rabbitmq' not in lower:
+ continue
+
+ def _sub_el(match):
+ current = int(match.group(2))
+ if current == target_major:
+ return match.group(0)
+ return match.group(1) + str(target_major) + match.group(3)
+
+ updated = _EL_URL_SEGMENT.sub(_sub_el, original)
+ if updated == original:
+ continue
+ tmp_path = None
+ try:
+ fd, tmp_path = tempfile.mkstemp(
+ prefix='.cybercp-rabbitmq-',
+ suffix='.tmp',
+ dir=_YUM_REPOS_D,
+ text=True,
+ )
+ with os.fdopen(fd, 'w', encoding='utf-8') as out:
+ out.write(updated)
+ os.replace(tmp_path, repo_path)
+ tmp_path = None
+ _write_status(
+ status_file,
+ 'Aligned RabbitMQ Packagecloud repo {0} to el/{1}.'.format(
+ repo_name, target_major
+ )
+ )
+ except PermissionError:
+ _write_status(
+ status_file,
+ 'rabbitmq repo align: need root to rewrite {0} (el/{1}).'.format(
+ repo_name, target_major
+ )
+ )
+ except OSError as err:
+ _write_status(
+ status_file,
+ 'rabbitmq repo align: {0}: {1}'.format(repo_name, str(err)[:120])
+ )
+ finally:
+ if tmp_path and os.path.isfile(tmp_path):
+ try:
+ os.unlink(tmp_path)
+ except OSError:
+ pass
+
+
+def refresh_debian_apt_metadata(status_file=None):
+ """Second-chance apt metadata refresh without re-running Packagecloud scripts."""
+ if not is_debian_family():
+ return True
+ last_err = ''
+ for apt_attempt in range(1, 4):
+ a_rc, _, a_err = _run(['apt-get', 'update', '-y'], timeout=180)
+ if a_rc == 0:
+ _write_status(
+ status_file,
+ 'APT metadata refreshed (attempt {0}).'.format(apt_attempt)
+ )
+ return True
+ last_err = (a_err or '').strip()
+ _write_status(
+ status_file,
+ 'apt-get update attempt {0}: {1}'.format(apt_attempt, (last_err or '')[:160])
+ )
+ time.sleep(min(3 * apt_attempt, 12))
+ return False
+
+
def ensure_rabbitmq_team_repos(stream, status_file=None):
"""
Idempotently enable rabbitmq-erlang and rabbitmq-server Packagecloud repos.
@@ -84,8 +272,17 @@ def ensure_rabbitmq_team_repos(stream, status_file=None):
_write_status(
status_file, 'rabbitmq-server repo script: ' + (err2 or out2 or 'failed')
)
- _run(['apt-get', 'update', '-y'], timeout=120)
+ for apt_attempt in range(1, 4):
+ a_rc, _, a_err = _run(['apt-get', 'update', '-y'], timeout=180)
+ if a_rc == 0:
+ break
+ _write_status(
+ status_file,
+ 'apt-get update attempt {0}: {1}'.format(apt_attempt, (a_err or '')[:160])
+ )
+ time.sleep(min(3 * apt_attempt, 12))
else:
+ align_rabbitmq_packagecloud_repos_to_os(status_file=status_file)
rc, out, err = _run_shell_trusted(_RPM_ERLANG_SCRIPT)
if rc != 0:
_write_status(status_file, 'rabbitmq-erlang repo script: ' + (err or out or 'failed'))
@@ -94,11 +291,11 @@ def ensure_rabbitmq_team_repos(stream, status_file=None):
_write_status(
status_file, 'rabbitmq-server repo script: ' + (err2 or out2 or 'failed')
)
- # Prefer dnf; yum exists as symlink on EL8/9.
- for cache_cmd in (['dnf', 'makecache', '-y'], ['yum', 'makecache', '-y']):
- c_rc, _, _ = _run(cache_cmd, timeout=120)
- if c_rc == 0:
- break
+ # 4.x builds may appear after a fresh metadata pull; expire + retries help visibility.
+ _rhel_refresh_package_metadata(
+ status_file=status_file,
+ aggressive=(stream == '4'),
+ )
_write_status(status_file, 'Team RabbitMQ repositories ready.')
diff --git a/manageServices/application_versions.py b/manageServices/application_versions.py
index 4d6b2b8e3..44d3ad837 100644
--- a/manageServices/application_versions.py
+++ b/manageServices/application_versions.py
@@ -1,10 +1,15 @@
import os
+import platform
import re
import subprocess
import threading
import time
-from manageServices.application_detection import is_debian_family, package_name_for_app
+from manageServices.application_detection import (
+ is_debian_family,
+ package_name_for_app,
+ rhel_major_from_os_release,
+)
# applicationMeta can call get_available_versions many times per request (ES 7/8/9, RMQ 3/4).
# Concurrent DNF from every WSGI worker exhausts lscpd and returns HTTP 503. Cache + serialize cold fetches.
@@ -184,27 +189,37 @@ def _dnf_reposdir_flag(use_cyberpanel_extra):
return ['--setopt=reposdir=/etc/yum.repos.d,{0}'.format(_CYBERPANEL_DNF_EXTRA)]
-def _rhel_repoquery_versions(pkg_name, use_cyberpanel_extra_repos=False, enablerepos=None):
+def _rhel_repoquery_versions(
+ pkg_name,
+ use_cyberpanel_extra_repos=False,
+ enablerepos=None,
+ latest_limit=50,
+ normalize_max=25,
+):
"""
Resolve distinct %{version} strings from enabled repos.
RPM NEVRA text parsing is brittle (el9_7 etc.); repoquery --qf is reliable.
+
+ For RabbitMQ, pass latest_limit=None (no cap — el8-tagged RPMs may share metadata
+ with EL9) and normalize_max=200 so stream filtering (3.x vs 4.x) is not fed only
+ the newest majors (which would hide the other line entirely).
"""
- cmd = (
+ dnf_cmd = (
['dnf']
+ _dnf_reposdir_flag(use_cyberpanel_extra_repos)
+ [
'repoquery',
+ '--available',
'--show-duplicates',
- '--latest-limit=50',
- '--qf',
- '%{version}',
- pkg_name,
]
)
+ if latest_limit is not None:
+ dnf_cmd.append('--latest-limit={0}'.format(int(latest_limit)))
+ dnf_cmd.extend(['--qf', '%{version}', pkg_name])
if enablerepos:
for repo_id in enablerepos:
- cmd.extend(['--enablerepo', repo_id])
- rc, out, err = _run(cmd, timeout=180)
+ dnf_cmd.extend(['--enablerepo', repo_id])
+ rc, out, err = _run(dnf_cmd, timeout=240)
raw = []
if rc == 0 and out.strip():
for line in out.splitlines():
@@ -212,13 +227,14 @@ def _rhel_repoquery_versions(pkg_name, use_cyberpanel_extra_repos=False, enabler
if v and re.match(r'^[0-9]', v):
raw.append(v)
if raw:
- return _normalize_versions(_sort_versions_desc(raw))
+ return _normalize_versions(_sort_versions_desc(raw), max_items=normalize_max)
# Legacy systems / fallback
- rc2, out2, _ = _run(
- ['yum', 'repoquery', '--show-duplicates', '--qf', '%{version}', pkg_name],
- timeout=120,
- )
+ yum_cmd = ['yum', 'repoquery', '--available', '--show-duplicates']
+ if latest_limit is not None:
+ yum_cmd.append('--latest-limit={0}'.format(int(latest_limit)))
+ yum_cmd.extend(['--qf', '%{version}', pkg_name])
+ rc2, out2, _ = _run(yum_cmd, timeout=120)
raw2 = []
if rc2 == 0 and out2.strip():
for line in out2.splitlines():
@@ -226,10 +242,11 @@ def _rhel_repoquery_versions(pkg_name, use_cyberpanel_extra_repos=False, enabler
if v and re.match(r'^[0-9]', v):
raw2.append(v)
if raw2:
- return _normalize_versions(_sort_versions_desc(raw2))
+ return _normalize_versions(_sort_versions_desc(raw2), max_items=normalize_max)
# Oldest fallback: yum list
rc3, out3, _ = _run(['yum', '--showduplicates', 'list', pkg_name], timeout=120)
+ raw3 = []
if rc3 == 0:
for line in out3.splitlines():
row = line.strip()
@@ -237,13 +254,54 @@ def _rhel_repoquery_versions(pkg_name, use_cyberpanel_extra_repos=False, enabler
continue
fields = row.split()
if len(fields) >= 2 and pkg_name in fields[0]:
- raw2.append(fields[1])
- if raw2:
- return _normalize_versions(_sort_versions_desc(raw2))
+ raw3.append(fields[1])
+ if raw3:
+ return _normalize_versions(_sort_versions_desc(raw3), max_items=normalize_max)
return []
-def _debian_versions(pkg_name):
+def _merge_version_candidates(primary, extra, normalize_max=200):
+ """Dedupe and sort descending for RabbitMQ multi-source repoquery."""
+ return _normalize_versions(
+ _sort_versions_desc(list(primary or []) + list(extra or [])),
+ max_items=normalize_max,
+ )
+
+
+def _rhel_repoquery_rabbitmq_packagecloud_el_dist(pkg_name, el_major):
+ """
+ Query rabbitmq-server versions from a specific Packagecloud el/N path without
+ enabling that repo system-wide. Helps when el/9 metadata lags el/8 for 4.x.
+ """
+ arch = platform.machine() or 'x86_64'
+ repoid = 'cybercp-pc-rmq-el{0}'.format(int(el_major))
+ base = 'https://packagecloud.io/rabbitmq/rabbitmq-server/el/{0}/{1}'.format(
+ int(el_major), arch
+ )
+ cmd = [
+ 'dnf',
+ 'repoquery',
+ '--repofrompath={0},{1}'.format(repoid, base),
+ '--setopt={0}.gpgcheck=0'.format(repoid),
+ '--setopt={0}.repo_gpgcheck=0'.format(repoid),
+ '--available',
+ '--show-duplicates',
+ '--qf',
+ '%{version}',
+ pkg_name,
+ ]
+ rc, out, _ = _run(cmd, timeout=240)
+ if rc != 0 or not (out or '').strip():
+ return []
+ raw = []
+ for line in out.splitlines():
+ v = (line or '').strip()
+ if v and re.match(r'^[0-9]', v):
+ raw.append(v)
+ return _normalize_versions(_sort_versions_desc(raw), max_items=200)
+
+
+def _debian_versions(pkg_name, normalize_max=25):
versions = []
_run(['apt-get', 'update', '-y'], timeout=180)
rc, out, _ = _run(['apt-cache', 'madison', pkg_name], timeout=60)
@@ -259,7 +317,7 @@ def _debian_versions(pkg_name):
for v in versions:
m = re.search(r'(\d+\.\d+\.\d+)', v)
collected.append(m.group(1) if m else v)
- return _normalize_versions(_sort_versions_desc(collected))
+ return _normalize_versions(_sort_versions_desc(collected), max_items=normalize_max)
def _filter_es_major(versions, es_major):
@@ -272,7 +330,7 @@ def _filter_es_major(versions, es_major):
return out
-def _get_available_versions_uncached(app_name, es_major='8', rabbitmq_stream='3'):
+def _get_available_versions_uncached(app_name, es_major='8', rabbitmq_stream='4'):
pkg_name = package_name_for_app(app_name)
if app_name == 'Elasticsearch':
pkg_name = 'elasticsearch'
@@ -280,18 +338,20 @@ def _get_available_versions_uncached(app_name, es_major='8', rabbitmq_stream='3'
if not pkg_name:
return []
- rmq_stream = '3'
+ rmq_stream = '4'
if app_name == 'RabbitMQ':
from manageServices.application_rabbitmq_repo import (
normalize_rabbitmq_stream,
ensure_rabbitmq_team_repos,
- filter_versions_for_stream,
)
rmq_stream = normalize_rabbitmq_stream(rabbitmq_stream)
ensure_rabbitmq_team_repos(rmq_stream)
if is_debian_family():
- versions = _debian_versions(pkg_name)
+ if app_name == 'RabbitMQ':
+ versions = _debian_versions(pkg_name, normalize_max=200)
+ else:
+ versions = _debian_versions(pkg_name)
if app_name == 'Elasticsearch':
versions = _filter_es_major(versions, es_major)
else:
@@ -301,16 +361,50 @@ def _get_available_versions_uncached(app_name, es_major='8', rabbitmq_stream='3'
pkg_name, use_cyberpanel_extra_repos=True
)
versions = _filter_es_major(versions, es_major)
+ elif app_name == 'RabbitMQ':
+ versions = _rhel_repoquery_versions(
+ pkg_name, latest_limit=None, normalize_max=200
+ )
+ host_major = rhel_major_from_os_release()
+ # el/9 (and newer) enabled repos often omit 4.x in metadata; el/8 tree may list them.
+ if host_major is not None and host_major >= 9:
+ pc_el8 = _rhel_repoquery_rabbitmq_packagecloud_el_dist(pkg_name, 8)
+ if pc_el8:
+ versions = _merge_version_candidates(versions, pc_el8, 200)
else:
versions = _rhel_repoquery_versions(pkg_name)
if app_name == 'RabbitMQ':
- from manageServices.application_rabbitmq_repo import filter_versions_for_stream
+ from manageServices.application_rabbitmq_repo import (
+ RABBITMQ_4X_METADATA_FALLBACK_VERSIONS,
+ filter_versions_for_stream,
+ refresh_debian_apt_metadata,
+ refresh_rhel_metadata_for_rabbitmq_repos,
+ )
versions = filter_versions_for_stream(versions, rmq_stream)
+ if not versions:
+ if is_debian_family():
+ refresh_debian_apt_metadata()
+ versions = _debian_versions(pkg_name, normalize_max=200)
+ else:
+ refresh_rhel_metadata_for_rabbitmq_repos()
+ versions = _rhel_repoquery_versions(
+ pkg_name, latest_limit=None, normalize_max=200
+ )
+ host_major = rhel_major_from_os_release()
+ if host_major is not None and host_major >= 9:
+ pc_el8 = _rhel_repoquery_rabbitmq_packagecloud_el_dist(pkg_name, 8)
+ if pc_el8:
+ versions = _merge_version_candidates(versions, pc_el8, 200)
+ versions = filter_versions_for_stream(versions, rmq_stream)
+ # Always offer GA 4.x when DNF lists none (panel user may get empty repoquery).
+ if rmq_stream == '4' and not versions and not is_debian_family():
+ versions = list(RABBITMQ_4X_METADATA_FALLBACK_VERSIONS)
+ versions = _normalize_versions(_sort_versions_desc(versions), max_items=40)
return versions
-def get_available_versions(app_name, es_major='8', rabbitmq_stream='3'):
+def get_available_versions(app_name, es_major='8', rabbitmq_stream='4'):
"""
Cached wrapper: avoids hammering DNF from many concurrent panel workers (503 on Manage Applications).
"""
@@ -331,14 +425,14 @@ def get_available_versions(app_name, es_major='8', rabbitmq_stream='3'):
return list(versions)
-def get_latest_version(app_name, es_major='8', rabbitmq_stream='3'):
+def get_latest_version(app_name, es_major='8', rabbitmq_stream='4'):
versions = get_available_versions(app_name, es_major, rabbitmq_stream)
if not versions:
return ''
return versions[0]
-def get_branch_and_global_latest(app_name, es_major='8', rabbitmq_stream='3'):
+def get_branch_and_global_latest(app_name, es_major='8', rabbitmq_stream='4'):
"""
Latest on the UI-selected branch/stream vs latest across all supported branches.
diff --git a/manageServices/serviceManager.py b/manageServices/serviceManager.py
index 9518da6c2..a08b3e75b 100644
--- a/manageServices/serviceManager.py
+++ b/manageServices/serviceManager.py
@@ -193,7 +193,7 @@ def main():
parser.add_argument('--action', help='Action to run: install|remove|upgrade')
parser.add_argument('--version', default='latest', help='Target package version or latest')
parser.add_argument('--esMajor', default='8', help='Elasticsearch major stream (7|8|9)')
- parser.add_argument('--rabbitmqStream', default='3', help='RabbitMQ major stream (3|4)')
+ parser.add_argument('--rabbitmqStream', default='4', help='RabbitMQ major stream (3|4)')
args = vars(parser.parse_args())
@@ -221,12 +221,12 @@ def main():
elif args["function"] == "InstallRabbitMQ":
ServiceManager.InstallRabbitMQ(
version=args.get('version', 'latest'),
- stream=args.get('rabbitmqStream', '3'),
+ stream=args.get('rabbitmqStream', '4'),
)
elif args["function"] == "UpgradeRabbitMQ":
ServiceManager.UpgradeRabbitMQ(
version=args.get('version', 'latest'),
- stream=args.get('rabbitmqStream', '3'),
+ stream=args.get('rabbitmqStream', '4'),
)
elif args["function"] == "RemoveRabbitMQ":
ServiceManager.RemoveRabbitMQ()
@@ -235,7 +235,7 @@ def main():
action = args.get("action").lower()
version = args.get("version", "latest")
es_major = args.get("esMajor", "8")
- rmq_stream = args.get("rabbitmqStream", "3")
+ rmq_stream = args.get("rabbitmqStream", "4")
if app_name == 'Elasticsearch':
if action == 'install':
diff --git a/manageServices/static/manageServices/manageServices.js b/manageServices/static/manageServices/manageServices.js
index 057ce349e..203436de1 100644
--- a/manageServices/static/manageServices/manageServices.js
+++ b/manageServices/static/manageServices/manageServices.js
@@ -477,6 +477,26 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
return out;
}
+ function versionMatchesRabbitmqStream(ver, stream) {
+ var s = String(stream || '4').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === s);
+ }
+
+ function versionMatchesEsMajor(ver, major) {
+ var mjr = String(major || '8').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === mjr);
+ }
+
$scope.versionLabel = function (v) {
if (v === 'latest') {
return 'latest';
@@ -535,7 +555,8 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
versions: vers,
latestAvailable: meta.latestAvailable || '',
- latestOverall: meta.latestOverall || ''
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
};
});
} catch (ignore) {
@@ -581,10 +602,33 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
};
$scope.selectedEsMajor = '8';
- $scope.selectedRabbitmqStream = '3';
+ $scope.selectedRabbitmqStream = '4';
+ /** RabbitMQ: 4.x is default for new installs (metadata prefetched). Upgrade may require picking stream if version line is unknown. ES major still user-picked before version list loads. */
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
$scope.confirmAction = false;
$scope.selectedCurrentVersion = '';
+ $scope.chooseRabbitmqStream = function (stream) {
+ var s = String(stream || '4').trim();
+ if (s !== '3' && s !== '4') {
+ s = '4';
+ }
+ $scope.selectedRabbitmqStream = s;
+ $scope.rabbitmqBranchChosen = true;
+ $scope.refreshMeta();
+ };
+
+ $scope.chooseEsMajor = function (major) {
+ var m = String(major || '8').trim();
+ if (m !== '7' && m !== '8' && m !== '9') {
+ m = '8';
+ }
+ $scope.selectedEsMajor = m;
+ $scope.esMajorChosen = true;
+ $scope.refreshMeta();
+ };
+
/**
* When the install/upgrade modal is open, re-apply version list from latest applicationMeta.
* (Page-load meta can be empty for ES if dnf was slow; opening the modal must refetch.)
@@ -596,13 +640,35 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
if ($scope.appName !== 'Elasticsearch' && $scope.appName !== 'Redis' && $scope.appName !== 'RabbitMQ') {
return;
}
+ if ($scope.appName === 'RabbitMQ' && !$scope.rabbitmqBranchChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
+ if ($scope.appName === 'Elasticsearch' && !$scope.esMajorChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
var meta = $scope.findAppMeta($scope.appName);
var vers = sanitizeVersionsArray((meta && meta.versions) ? meta.versions : []);
$scope.selectedVersions = ['latest'].concat(vers);
var curRaw = (meta && meta.installedVersion) ? meta.installedVersion : ($scope.selectedCurrentVersion || '');
var cur = normalizeVersionToken(curRaw) || String(curRaw || '').trim();
if (cur && $scope.selectedVersions.indexOf(cur) === -1) {
- $scope.selectedVersions.push(cur);
+ var allowCur = true;
+ if ($scope.appName === 'RabbitMQ') {
+ allowCur = versionMatchesRabbitmqStream(cur, $scope.selectedRabbitmqStream);
+ } else if ($scope.appName === 'Elasticsearch') {
+ allowCur = versionMatchesEsMajor(cur, $scope.selectedEsMajor);
+ }
+ if (allowCur) {
+ $scope.selectedVersions.push(cur);
+ }
}
if (cur) {
$scope.selectedCurrentVersion = cur;
@@ -646,6 +712,16 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
(payload.apps || []).forEach(function (app) {
appMap[app.name] = app;
});
+ var esMetaResp = appMap['Elasticsearch'];
+ var rmqMetaResp = appMap['RabbitMQ'];
+ var respEsMaj = String(esMetaResp && esMetaResp.major != null ? esMetaResp.major : '').trim();
+ if (respEsMaj && respEsMaj !== String($scope.selectedEsMajor || '8').trim()) {
+ return;
+ }
+ var respRmqStream = String(rmqMetaResp && rmqMetaResp.rabbitmqStream != null ? rmqMetaResp.rabbitmqStream : '').trim();
+ if (respRmqStream && respRmqStream !== String($scope.selectedRabbitmqStream || '4').trim()) {
+ return;
+ }
$scope.apps = $scope.apps.map(function (baseApp) {
var meta = appMap[baseApp.name] || {};
var vers = meta.versions;
@@ -662,7 +738,8 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
versions: vers,
latestAvailable: meta.latestAvailable || '',
- latestOverall: meta.latestOverall || ''
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
};
});
$scope.syncModalVersionLists();
@@ -700,6 +777,13 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
$scope.selectedRabbitmqStream = '4';
} else if (effectiveInstalled && /^3\./.test(effectiveInstalled)) {
$scope.selectedRabbitmqStream = '3';
+ } else if (status === 'Installing') {
+ $scope.selectedRabbitmqStream = '4';
+ }
+ if (status === 'Upgrading' && effectiveInstalled) {
+ if (/^4\./.test(effectiveInstalled) || /^3\./.test(effectiveInstalled)) {
+ $scope.rabbitmqBranchChosen = true;
+ }
}
}
@@ -715,13 +799,17 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
$scope.selectedVersions = ['latest'];
- var svcVers = sanitizeVersionsArray(service.versions || []);
- if (svcVers.length > 0) {
- $scope.selectedVersions = ['latest'].concat(svcVers);
- }
- var curPick = normalizeVersionToken(effectiveInstalled) || effectiveInstalled;
- if (curPick && $scope.selectedVersions.indexOf(curPick) === -1) {
- $scope.selectedVersions.push(curPick);
+ var deferVersionList = (service.name === 'RabbitMQ' && !$scope.rabbitmqBranchChosen)
+ || (service.name === 'Elasticsearch' && !$scope.esMajorChosen);
+ if (!deferVersionList) {
+ var svcVers = sanitizeVersionsArray(service.versions || []);
+ if (svcVers.length > 0) {
+ $scope.selectedVersions = ['latest'].concat(svcVers);
+ }
+ var curPick = normalizeVersionToken(effectiveInstalled) || effectiveInstalled;
+ if (curPick && $scope.selectedVersions.indexOf(curPick) === -1) {
+ $scope.selectedVersions.push(curPick);
+ }
}
$scope.selectedVersion = 'latest';
$scope.requestData = '';
@@ -729,7 +817,11 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
var realVers = ($scope.selectedVersions || []).filter(function (v) {
return v && v !== 'latest';
});
- $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ if (deferVersionList) {
+ $scope.repoShowsOnlyOneStream = false;
+ } else {
+ $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ }
$scope.recalcSelectedVersionRowIndex();
};
@@ -775,44 +867,28 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
};
if (needMeta) {
- // applicationMeta uses selectedRabbitmqStream / selectedEsMajor. If the user left
- // 4.x selected earlier, the first POST would ask for 4.x while the node is on 3.x —
- // filtered versions come back empty and the Version dropdown shows only "latest".
- var bootVer = (bootstrapInstalledVersion !== undefined && bootstrapInstalledVersion !== null)
- ? String(bootstrapInstalledVersion).trim()
- : '';
- if (!bootVer && appName) {
- var metaEarly = $scope.findAppMeta(appName);
- if (metaEarly && metaEarly.installedVersion) {
- bootVer = String(metaEarly.installedVersion).trim();
- }
- }
- if (appName === 'RabbitMQ' && bootVer) {
- if (/^4\./.test(bootVer)) {
+ if (appName === 'RabbitMQ') {
+ if (status === 'Installing') {
$scope.selectedRabbitmqStream = '4';
- } else if (/^3\./.test(bootVer)) {
- $scope.selectedRabbitmqStream = '3';
+ $scope.rabbitmqBranchChosen = true;
+ } else {
+ $scope.rabbitmqBranchChosen = false;
}
}
- if (appName === 'Elasticsearch' && bootVer) {
- var biv = bootVer;
- if (/^9\./.test(biv)) {
- $scope.selectedEsMajor = '9';
- } else if (/^8\./.test(biv)) {
- $scope.selectedEsMajor = '8';
- } else if (/^7\./.test(biv)) {
- $scope.selectedEsMajor = '7';
- }
+ if (appName === 'Elasticsearch') {
+ $scope.esMajorChosen = false;
}
- // Open immediately with bootstrap / in-memory meta; refresh in background.
- // Blocking on applicationMeta made "Change version" feel frozen (dnf/repoquery).
+ // RabbitMQ install: default 4.x stream and prefetch metadata. Upgrade: pick stream from installed version.
+ // Elasticsearch: still wait for user major. Redis refreshes on open.
$scope.appName = appName;
$scope.status = status;
$scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
showModal();
- $timeout(function () {
- $scope.refreshMeta();
- }, 0);
+ if (appName === 'Redis' || (appName === 'RabbitMQ' && $scope.rabbitmqBranchChosen)) {
+ $timeout(function () {
+ $scope.refreshMeta();
+ }, 0);
+ }
} else {
$scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
showModal();
@@ -833,6 +909,22 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
});
return;
}
+ if (appName === 'RabbitMQ' && (status === 'Installing' || status === 'Upgrading') && !$scope.rabbitmqBranchChosen) {
+ new PNotify({
+ title: 'Stream required',
+ text: 'Choose RabbitMQ 4.x or 3.x above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
+ if (appName === 'Elasticsearch' && (status === 'Installing' || status === 'Upgrading') && !$scope.esMajorChosen) {
+ new PNotify({
+ title: 'Major version required',
+ text: 'Choose Elasticsearch major (7, 8, or 9) above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
$scope.status = status;
$scope.appName = appName;
@@ -845,7 +937,7 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
status: status,
version: $scope.selectedVersion || 'latest',
esMajor: $scope.selectedEsMajor || '8',
- rabbitmqStream: $scope.selectedRabbitmqStream || '3',
+ rabbitmqStream: $scope.selectedRabbitmqStream || '4',
confirmAction: $scope.confirmAction === true
};
@@ -932,6 +1024,15 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
// Do not fetch package metadata on page load; it can block workers under DNF load.
// Metadata is fetched on-demand when opening install/version-change modals.
+ if (typeof window.jQuery !== 'undefined' && jQuery.fn.on) {
+ jQuery('#settings').on('hidden.bs.modal', function () {
+ $scope.$evalAsync(function () {
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
+ });
+ });
+ }
+
});
/* Java script code */
\ No newline at end of file
diff --git a/manageServices/templates/manageServices/applications.html b/manageServices/templates/manageServices/applications.html
index fa4635613..cc029e1c8 100644
--- a/manageServices/templates/manageServices/applications.html
+++ b/manageServices/templates/manageServices/applications.html
@@ -587,7 +587,44 @@
-
+
+
+
+
{% trans "Major" %}:
+
+ {% trans "Select a major below — versions load after you choose." %}
+
+
+
+
+
+
+
+
+
+
+
+
{% trans "Stream" %}:
+ 4.x
+ 3.x ({% trans "maintenance" %})
+ {% trans "Select a stream below — versions load after you choose." %}
+
+
+
+
+
+
+
+ {% trans "Uses Team RabbitMQ Packagecloud repos; 4.x requires a compatible Erlang (OTP 26+). The installer will try to align Erlang when needed." %}
+ {% trans "Upstream RPMs may be named el8 but are still supported on AlmaLinux/RHEL/Rocky 9." %}
+
+
+
+
+
+
+
@@ -615,30 +652,6 @@
ng-if="status == 'Upgrading'">
{% trans "A backup of application data is created automatically before upgrading or downgrading. Data is merged into the new version after packages install; the backup is removed only after a successful start." %}
-
-
-
-
{% trans "Major" %}:
-
-
-
-
-
-
-
-
-
-
-
{% trans "Stream" %}:
-
-
-
-
-
-
- {% trans "Uses Team RabbitMQ Packagecloud repos; 4.x requires a compatible Erlang (OTP 26+). The installer will try to align Erlang when needed." %}
-
-
@@ -647,7 +660,8 @@
diff --git a/manageServices/views.py b/manageServices/views.py
index 20451cee4..fd6ffbc86 100644
--- a/manageServices/views.py
+++ b/manageServices/views.py
@@ -277,7 +277,7 @@ def saveStatus(request):
def manageApplications(request):
services, application_meta_bootstrap_json = build_manage_applications_page_data(
- '8', '3'
+ '8', '4'
)
proc = httpProc(
@@ -308,9 +308,9 @@ def applicationMeta(request):
if requested_major not in ('7', '8', '9'):
requested_major = '8'
- requested_rmq_stream = str(data.get('rabbitmqStream', '3')).strip()
+ requested_rmq_stream = str(data.get('rabbitmqStream', '4')).strip()
if requested_rmq_stream not in ('3', '4'):
- requested_rmq_stream = '3'
+ requested_rmq_stream = '4'
response_data = get_application_meta_response_dict(
requested_major, requested_rmq_stream
@@ -345,9 +345,9 @@ def removeInstall(request):
esMajor = str(data.get('esMajor', '8')).strip() or '8'
if esMajor not in ('7', '8', '9'):
esMajor = '8'
- rabbitmqStream = str(data.get('rabbitmqStream', '3')).strip() or '3'
+ rabbitmqStream = str(data.get('rabbitmqStream', '4')).strip() or '4'
if rabbitmqStream not in ('3', '4'):
- rabbitmqStream = '3'
+ rabbitmqStream = '4'
confirmAction = bool(data.get('confirmAction', False))
support = managed_apps_os_support()
diff --git a/public/static/manageServices/manageServices.js b/public/static/manageServices/manageServices.js
index 057ce349e..203436de1 100644
--- a/public/static/manageServices/manageServices.js
+++ b/public/static/manageServices/manageServices.js
@@ -477,6 +477,26 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
return out;
}
+ function versionMatchesRabbitmqStream(ver, stream) {
+ var s = String(stream || '4').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === s);
+ }
+
+ function versionMatchesEsMajor(ver, major) {
+ var mjr = String(major || '8').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === mjr);
+ }
+
$scope.versionLabel = function (v) {
if (v === 'latest') {
return 'latest';
@@ -535,7 +555,8 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
versions: vers,
latestAvailable: meta.latestAvailable || '',
- latestOverall: meta.latestOverall || ''
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
};
});
} catch (ignore) {
@@ -581,10 +602,33 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
};
$scope.selectedEsMajor = '8';
- $scope.selectedRabbitmqStream = '3';
+ $scope.selectedRabbitmqStream = '4';
+ /** RabbitMQ: 4.x is default for new installs (metadata prefetched). Upgrade may require picking stream if version line is unknown. ES major still user-picked before version list loads. */
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
$scope.confirmAction = false;
$scope.selectedCurrentVersion = '';
+ $scope.chooseRabbitmqStream = function (stream) {
+ var s = String(stream || '4').trim();
+ if (s !== '3' && s !== '4') {
+ s = '4';
+ }
+ $scope.selectedRabbitmqStream = s;
+ $scope.rabbitmqBranchChosen = true;
+ $scope.refreshMeta();
+ };
+
+ $scope.chooseEsMajor = function (major) {
+ var m = String(major || '8').trim();
+ if (m !== '7' && m !== '8' && m !== '9') {
+ m = '8';
+ }
+ $scope.selectedEsMajor = m;
+ $scope.esMajorChosen = true;
+ $scope.refreshMeta();
+ };
+
/**
* When the install/upgrade modal is open, re-apply version list from latest applicationMeta.
* (Page-load meta can be empty for ES if dnf was slow; opening the modal must refetch.)
@@ -596,13 +640,35 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
if ($scope.appName !== 'Elasticsearch' && $scope.appName !== 'Redis' && $scope.appName !== 'RabbitMQ') {
return;
}
+ if ($scope.appName === 'RabbitMQ' && !$scope.rabbitmqBranchChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
+ if ($scope.appName === 'Elasticsearch' && !$scope.esMajorChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
var meta = $scope.findAppMeta($scope.appName);
var vers = sanitizeVersionsArray((meta && meta.versions) ? meta.versions : []);
$scope.selectedVersions = ['latest'].concat(vers);
var curRaw = (meta && meta.installedVersion) ? meta.installedVersion : ($scope.selectedCurrentVersion || '');
var cur = normalizeVersionToken(curRaw) || String(curRaw || '').trim();
if (cur && $scope.selectedVersions.indexOf(cur) === -1) {
- $scope.selectedVersions.push(cur);
+ var allowCur = true;
+ if ($scope.appName === 'RabbitMQ') {
+ allowCur = versionMatchesRabbitmqStream(cur, $scope.selectedRabbitmqStream);
+ } else if ($scope.appName === 'Elasticsearch') {
+ allowCur = versionMatchesEsMajor(cur, $scope.selectedEsMajor);
+ }
+ if (allowCur) {
+ $scope.selectedVersions.push(cur);
+ }
}
if (cur) {
$scope.selectedCurrentVersion = cur;
@@ -646,6 +712,16 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
(payload.apps || []).forEach(function (app) {
appMap[app.name] = app;
});
+ var esMetaResp = appMap['Elasticsearch'];
+ var rmqMetaResp = appMap['RabbitMQ'];
+ var respEsMaj = String(esMetaResp && esMetaResp.major != null ? esMetaResp.major : '').trim();
+ if (respEsMaj && respEsMaj !== String($scope.selectedEsMajor || '8').trim()) {
+ return;
+ }
+ var respRmqStream = String(rmqMetaResp && rmqMetaResp.rabbitmqStream != null ? rmqMetaResp.rabbitmqStream : '').trim();
+ if (respRmqStream && respRmqStream !== String($scope.selectedRabbitmqStream || '4').trim()) {
+ return;
+ }
$scope.apps = $scope.apps.map(function (baseApp) {
var meta = appMap[baseApp.name] || {};
var vers = meta.versions;
@@ -662,7 +738,8 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
versions: vers,
latestAvailable: meta.latestAvailable || '',
- latestOverall: meta.latestOverall || ''
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
};
});
$scope.syncModalVersionLists();
@@ -700,6 +777,13 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
$scope.selectedRabbitmqStream = '4';
} else if (effectiveInstalled && /^3\./.test(effectiveInstalled)) {
$scope.selectedRabbitmqStream = '3';
+ } else if (status === 'Installing') {
+ $scope.selectedRabbitmqStream = '4';
+ }
+ if (status === 'Upgrading' && effectiveInstalled) {
+ if (/^4\./.test(effectiveInstalled) || /^3\./.test(effectiveInstalled)) {
+ $scope.rabbitmqBranchChosen = true;
+ }
}
}
@@ -715,13 +799,17 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
$scope.selectedVersions = ['latest'];
- var svcVers = sanitizeVersionsArray(service.versions || []);
- if (svcVers.length > 0) {
- $scope.selectedVersions = ['latest'].concat(svcVers);
- }
- var curPick = normalizeVersionToken(effectiveInstalled) || effectiveInstalled;
- if (curPick && $scope.selectedVersions.indexOf(curPick) === -1) {
- $scope.selectedVersions.push(curPick);
+ var deferVersionList = (service.name === 'RabbitMQ' && !$scope.rabbitmqBranchChosen)
+ || (service.name === 'Elasticsearch' && !$scope.esMajorChosen);
+ if (!deferVersionList) {
+ var svcVers = sanitizeVersionsArray(service.versions || []);
+ if (svcVers.length > 0) {
+ $scope.selectedVersions = ['latest'].concat(svcVers);
+ }
+ var curPick = normalizeVersionToken(effectiveInstalled) || effectiveInstalled;
+ if (curPick && $scope.selectedVersions.indexOf(curPick) === -1) {
+ $scope.selectedVersions.push(curPick);
+ }
}
$scope.selectedVersion = 'latest';
$scope.requestData = '';
@@ -729,7 +817,11 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
var realVers = ($scope.selectedVersions || []).filter(function (v) {
return v && v !== 'latest';
});
- $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ if (deferVersionList) {
+ $scope.repoShowsOnlyOneStream = false;
+ } else {
+ $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ }
$scope.recalcSelectedVersionRowIndex();
};
@@ -775,44 +867,28 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
};
if (needMeta) {
- // applicationMeta uses selectedRabbitmqStream / selectedEsMajor. If the user left
- // 4.x selected earlier, the first POST would ask for 4.x while the node is on 3.x —
- // filtered versions come back empty and the Version dropdown shows only "latest".
- var bootVer = (bootstrapInstalledVersion !== undefined && bootstrapInstalledVersion !== null)
- ? String(bootstrapInstalledVersion).trim()
- : '';
- if (!bootVer && appName) {
- var metaEarly = $scope.findAppMeta(appName);
- if (metaEarly && metaEarly.installedVersion) {
- bootVer = String(metaEarly.installedVersion).trim();
- }
- }
- if (appName === 'RabbitMQ' && bootVer) {
- if (/^4\./.test(bootVer)) {
+ if (appName === 'RabbitMQ') {
+ if (status === 'Installing') {
$scope.selectedRabbitmqStream = '4';
- } else if (/^3\./.test(bootVer)) {
- $scope.selectedRabbitmqStream = '3';
+ $scope.rabbitmqBranchChosen = true;
+ } else {
+ $scope.rabbitmqBranchChosen = false;
}
}
- if (appName === 'Elasticsearch' && bootVer) {
- var biv = bootVer;
- if (/^9\./.test(biv)) {
- $scope.selectedEsMajor = '9';
- } else if (/^8\./.test(biv)) {
- $scope.selectedEsMajor = '8';
- } else if (/^7\./.test(biv)) {
- $scope.selectedEsMajor = '7';
- }
+ if (appName === 'Elasticsearch') {
+ $scope.esMajorChosen = false;
}
- // Open immediately with bootstrap / in-memory meta; refresh in background.
- // Blocking on applicationMeta made "Change version" feel frozen (dnf/repoquery).
+ // RabbitMQ install: default 4.x stream and prefetch metadata. Upgrade: pick stream from installed version.
+ // Elasticsearch: still wait for user major. Redis refreshes on open.
$scope.appName = appName;
$scope.status = status;
$scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
showModal();
- $timeout(function () {
- $scope.refreshMeta();
- }, 0);
+ if (appName === 'Redis' || (appName === 'RabbitMQ' && $scope.rabbitmqBranchChosen)) {
+ $timeout(function () {
+ $scope.refreshMeta();
+ }, 0);
+ }
} else {
$scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
showModal();
@@ -833,6 +909,22 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
});
return;
}
+ if (appName === 'RabbitMQ' && (status === 'Installing' || status === 'Upgrading') && !$scope.rabbitmqBranchChosen) {
+ new PNotify({
+ title: 'Stream required',
+ text: 'Choose RabbitMQ 4.x or 3.x above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
+ if (appName === 'Elasticsearch' && (status === 'Installing' || status === 'Upgrading') && !$scope.esMajorChosen) {
+ new PNotify({
+ title: 'Major version required',
+ text: 'Choose Elasticsearch major (7, 8, or 9) above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
$scope.status = status;
$scope.appName = appName;
@@ -845,7 +937,7 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
status: status,
version: $scope.selectedVersion || 'latest',
esMajor: $scope.selectedEsMajor || '8',
- rabbitmqStream: $scope.selectedRabbitmqStream || '3',
+ rabbitmqStream: $scope.selectedRabbitmqStream || '4',
confirmAction: $scope.confirmAction === true
};
@@ -932,6 +1024,15 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
// Do not fetch package metadata on page load; it can block workers under DNF load.
// Metadata is fetched on-demand when opening install/version-change modals.
+ if (typeof window.jQuery !== 'undefined' && jQuery.fn.on) {
+ jQuery('#settings').on('hidden.bs.modal', function () {
+ $scope.$evalAsync(function () {
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
+ });
+ });
+ }
+
});
/* Java script code */
\ No newline at end of file
diff --git a/static/manageServices/manageServices.js b/static/manageServices/manageServices.js
index 949fab41c..203436de1 100644
--- a/static/manageServices/manageServices.js
+++ b/static/manageServices/manageServices.js
@@ -428,10 +428,503 @@ app.controller('pureFTPD', function ($scope, $http, $timeout, $window) {
app.controller('manageApplications', function ($scope, $http, $timeout, $window) {
+ /**
+ * Normalize entries from applicationMeta (strings, numbers, or rare object shapes)
+ * so version pickers never show blank rows. CyberPanel uses {$ ... $} interpolation
+ * in templates; list labels use versionLabel() for consistent display.
+ */
+ function normalizeVersionToken(v) {
+ if (v === null || v === undefined) {
+ return '';
+ }
+ if (v === 'latest') {
+ return 'latest';
+ }
+ if (typeof v === 'number' && isFinite(v)) {
+ return String(v);
+ }
+ if (angular.isObject(v)) {
+ var o = v;
+ var cand = o.version || o.Version || o.value || o.name || o.ver || o.label;
+ if (cand !== undefined && cand !== null) {
+ return String(cand).trim();
+ }
+ try {
+ return JSON.stringify(o);
+ } catch (ignore) {
+ return '';
+ }
+ }
+ return String(v).trim();
+ }
+
+ function sanitizeVersionsArray(vers) {
+ if (!angular.isArray(vers)) {
+ return [];
+ }
+ var out = [];
+ var seen = {};
+ vers.forEach(function (raw) {
+ var t = normalizeVersionToken(raw);
+ if (!t || t === 'latest') {
+ return;
+ }
+ if (!seen[t]) {
+ seen[t] = true;
+ out.push(t);
+ }
+ });
+ return out;
+ }
+
+ function versionMatchesRabbitmqStream(ver, stream) {
+ var s = String(stream || '4').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === s);
+ }
+
+ function versionMatchesEsMajor(ver, major) {
+ var mjr = String(major || '8').trim();
+ var t = normalizeVersionToken(ver);
+ if (!t || t === 'latest') {
+ return false;
+ }
+ var m = /^(\d+)\./.exec(t);
+ return !!(m && m[1] === mjr);
+ }
+
+ $scope.versionLabel = function (v) {
+ if (v === 'latest') {
+ return 'latest';
+ }
+ var t = normalizeVersionToken(v);
+ return t || '(unknown)';
+ };
+
+ $scope.versionTrackId = function (idx, v) {
+ return String(idx) + '|' + $scope.versionLabel(v);
+ };
+
+ /* false = long-running install/remove/poll (show spinners); true = idle */
$scope.cyberpanelLoading = true;
+ /** Background applicationMeta refresh — separate from cyberpanelLoading so the page does not “freeze” on every modal open. */
+ $scope.appsMetaRefreshing = false;
+ $scope.apps = [
+ {name: 'Elasticsearch', image: '/static/manageServices/images/elastic-search.png'},
+ {name: 'Redis', image: '/static/manageServices/images/redis.png'},
+ {name: 'RabbitMQ', image: '/static/manageServices/images/rabbitmq-logo.svg'}
+ ];
- $scope.removeInstall = function (appName, status) {
+ (function mergeMetaBootstrap() {
+ var el = document.getElementById('manageApplicationsMetaBootstrap');
+ if (!el || !el.textContent) {
+ return;
+ }
+ var raw = el.textContent.trim();
+ if (!raw) {
+ return;
+ }
+ try {
+ var boot = JSON.parse(raw);
+ if (!boot || Number(boot.status) !== 1) {
+ return;
+ }
+ var appMap = {};
+ (boot.apps || []).forEach(function (a) {
+ if (a && a.name) {
+ appMap[a.name] = a;
+ }
+ });
+ $scope.apps = $scope.apps.map(function (baseApp) {
+ var meta = appMap[baseApp.name] || {};
+ var vers = meta.versions;
+ if (!angular.isArray(vers)) {
+ vers = [];
+ }
+ vers = sanitizeVersionsArray(vers);
+ return {
+ name: baseApp.name,
+ image: baseApp.image,
+ installed: !!meta.installed,
+ installedVersion: meta.installedVersion || '',
+ updateAvailable: !!meta.updateAvailable,
+ crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
+ versions: vers,
+ latestAvailable: meta.latestAvailable || '',
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
+ };
+ });
+ } catch (ignore) {
+ /* keep bare apps list */
+ }
+ })();
+ $scope.selectedVersion = 'latest';
+ /** Row highlight uses $index so only one row looks selected (avoids sticky :focus / repeater quirks). */
+ $scope.selectedVersionRowIndex = 0;
+ $scope.selectedVersions = ['latest'];
+
+ $scope.recalcSelectedVersionRowIndex = function () {
+ var list = $scope.selectedVersions || [];
+ var sel = $scope.selectedVersion;
+ var i = list.indexOf(sel);
+ if (i < 0) {
+ var normSel = normalizeVersionToken(sel);
+ if (normSel) {
+ for (var j = 0; j < list.length; j += 1) {
+ if (normalizeVersionToken(list[j]) === normSel) {
+ i = j;
+ $scope.selectedVersion = list[j];
+ break;
+ }
+ }
+ }
+ }
+ $scope.selectedVersionRowIndex = (i >= 0) ? i : 0;
+ };
+
+ $scope.selectManagedAppVersion = function (idx, v, $event) {
+ var n = (typeof idx === 'number') ? idx : parseInt(idx, 10);
+ if (!isFinite(n) || n < 0) {
+ n = 0;
+ }
+ $scope.selectedVersionRowIndex = n;
+ $scope.selectedVersion = v;
+ if ($event && $event.target && typeof $event.target.blur === 'function') {
+ $event.target.blur();
+ } else if (typeof document !== 'undefined' && document.activeElement && typeof document.activeElement.blur === 'function') {
+ document.activeElement.blur();
+ }
+ };
+ $scope.selectedEsMajor = '8';
+ $scope.selectedRabbitmqStream = '4';
+ /** RabbitMQ: 4.x is default for new installs (metadata prefetched). Upgrade may require picking stream if version line is unknown. ES major still user-picked before version list loads. */
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
+ $scope.confirmAction = false;
+ $scope.selectedCurrentVersion = '';
+
+ $scope.chooseRabbitmqStream = function (stream) {
+ var s = String(stream || '4').trim();
+ if (s !== '3' && s !== '4') {
+ s = '4';
+ }
+ $scope.selectedRabbitmqStream = s;
+ $scope.rabbitmqBranchChosen = true;
+ $scope.refreshMeta();
+ };
+
+ $scope.chooseEsMajor = function (major) {
+ var m = String(major || '8').trim();
+ if (m !== '7' && m !== '8' && m !== '9') {
+ m = '8';
+ }
+ $scope.selectedEsMajor = m;
+ $scope.esMajorChosen = true;
+ $scope.refreshMeta();
+ };
+
+ /**
+ * When the install/upgrade modal is open, re-apply version list from latest applicationMeta.
+ * (Page-load meta can be empty for ES if dnf was slow; opening the modal must refetch.)
+ */
+ $scope.syncModalVersionLists = function () {
+ if (!$scope.appName || ($scope.status !== 'Installing' && $scope.status !== 'Upgrading')) {
+ return;
+ }
+ if ($scope.appName !== 'Elasticsearch' && $scope.appName !== 'Redis' && $scope.appName !== 'RabbitMQ') {
+ return;
+ }
+ if ($scope.appName === 'RabbitMQ' && !$scope.rabbitmqBranchChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
+ if ($scope.appName === 'Elasticsearch' && !$scope.esMajorChosen) {
+ $scope.selectedVersions = ['latest'];
+ $scope.selectedVersion = 'latest';
+ $scope.repoShowsOnlyOneStream = false;
+ $scope.recalcSelectedVersionRowIndex();
+ return;
+ }
+ var meta = $scope.findAppMeta($scope.appName);
+ var vers = sanitizeVersionsArray((meta && meta.versions) ? meta.versions : []);
+ $scope.selectedVersions = ['latest'].concat(vers);
+ var curRaw = (meta && meta.installedVersion) ? meta.installedVersion : ($scope.selectedCurrentVersion || '');
+ var cur = normalizeVersionToken(curRaw) || String(curRaw || '').trim();
+ if (cur && $scope.selectedVersions.indexOf(cur) === -1) {
+ var allowCur = true;
+ if ($scope.appName === 'RabbitMQ') {
+ allowCur = versionMatchesRabbitmqStream(cur, $scope.selectedRabbitmqStream);
+ } else if ($scope.appName === 'Elasticsearch') {
+ allowCur = versionMatchesEsMajor(cur, $scope.selectedEsMajor);
+ }
+ if (allowCur) {
+ $scope.selectedVersions.push(cur);
+ }
+ }
+ if (cur) {
+ $scope.selectedCurrentVersion = cur;
+ }
+ var prevSel = $scope.selectedVersion;
+ if (prevSel && $scope.selectedVersions.indexOf(prevSel) !== -1) {
+ $scope.selectedVersion = prevSel;
+ } else {
+ $scope.selectedVersion = 'latest';
+ }
+ var realVers = ($scope.selectedVersions || []).filter(function (v) {
+ return v && v !== 'latest';
+ });
+ $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ $scope.recalcSelectedVersionRowIndex();
+ };
+
+ $scope.refreshMeta = function () {
+ $scope.appsMetaRefreshing = true;
+ var url = "/manageservices/applicationMeta";
+ var data = {
+ esMajor: $scope.selectedEsMajor,
+ rabbitmqStream: $scope.selectedRabbitmqStream
+ };
+ var config = {
+ headers: {
+ 'Content-Type': 'application/json;charset=UTF-8',
+ 'X-CSRFToken': getCookie('csrftoken')
+ },
+ transformRequest: function (payload) {
+ return angular.toJson(payload);
+ }
+ };
+
+ return $http.post(url, data, config).then(function (response) {
+ $scope.appsMetaRefreshing = false;
+ var payload = response.data;
+ var ok = payload && (payload.status === 1 || payload.status === '1');
+ if (ok) {
+ var appMap = {};
+ (payload.apps || []).forEach(function (app) {
+ appMap[app.name] = app;
+ });
+ var esMetaResp = appMap['Elasticsearch'];
+ var rmqMetaResp = appMap['RabbitMQ'];
+ var respEsMaj = String(esMetaResp && esMetaResp.major != null ? esMetaResp.major : '').trim();
+ if (respEsMaj && respEsMaj !== String($scope.selectedEsMajor || '8').trim()) {
+ return;
+ }
+ var respRmqStream = String(rmqMetaResp && rmqMetaResp.rabbitmqStream != null ? rmqMetaResp.rabbitmqStream : '').trim();
+ if (respRmqStream && respRmqStream !== String($scope.selectedRabbitmqStream || '4').trim()) {
+ return;
+ }
+ $scope.apps = $scope.apps.map(function (baseApp) {
+ var meta = appMap[baseApp.name] || {};
+ var vers = meta.versions;
+ if (!angular.isArray(vers)) {
+ vers = [];
+ }
+ vers = sanitizeVersionsArray(vers);
+ return {
+ name: baseApp.name,
+ image: baseApp.image,
+ installed: !!meta.installed,
+ installedVersion: meta.installedVersion || '',
+ updateAvailable: !!meta.updateAvailable,
+ crossBranchUpdateSuggested: !!meta.crossBranchUpdateSuggested,
+ versions: vers,
+ latestAvailable: meta.latestAvailable || '',
+ latestOverall: meta.latestOverall || '',
+ rabbitmqVersionsHint: meta.rabbitmqVersionsHint || ''
+ };
+ });
+ $scope.syncModalVersionLists();
+ } else {
+ new PNotify({
+ title: 'Operation Failed!',
+ text: (payload && (payload.error_message || payload.errorMessage)) || 'Could not load application metadata.',
+ type: 'error'
+ });
+ }
+ }, function () {
+ $scope.appsMetaRefreshing = false;
+ new PNotify({
+ title: 'Operation Failed!',
+ text: 'Could not connect to server, please refresh this page',
+ type: 'error'
+ });
+ });
+ };
+
+ $scope.prepareAction = function (service, status, bootstrapInstalledVersion) {
+ if (bootstrapInstalledVersion === undefined || bootstrapInstalledVersion === null) {
+ bootstrapInstalledVersion = '';
+ } else {
+ bootstrapInstalledVersion = String(bootstrapInstalledVersion).trim();
+ }
+ $scope.status = status;
+ $scope.appName = service.name;
+ $scope.confirmAction = false;
+ var effectiveInstalled = (service.installedVersion || bootstrapInstalledVersion || '').trim();
+ $scope.selectedCurrentVersion = effectiveInstalled;
+
+ if (service.name === 'RabbitMQ') {
+ if (effectiveInstalled && /^4\./.test(effectiveInstalled)) {
+ $scope.selectedRabbitmqStream = '4';
+ } else if (effectiveInstalled && /^3\./.test(effectiveInstalled)) {
+ $scope.selectedRabbitmqStream = '3';
+ } else if (status === 'Installing') {
+ $scope.selectedRabbitmqStream = '4';
+ }
+ if (status === 'Upgrading' && effectiveInstalled) {
+ if (/^4\./.test(effectiveInstalled) || /^3\./.test(effectiveInstalled)) {
+ $scope.rabbitmqBranchChosen = true;
+ }
+ }
+ }
+
+ if (service.name === 'Elasticsearch' && effectiveInstalled) {
+ var iv = effectiveInstalled;
+ if (/^9\./.test(iv)) {
+ $scope.selectedEsMajor = '9';
+ } else if (/^8\./.test(iv)) {
+ $scope.selectedEsMajor = '8';
+ } else if (/^7\./.test(iv)) {
+ $scope.selectedEsMajor = '7';
+ }
+ }
+
+ $scope.selectedVersions = ['latest'];
+ var deferVersionList = (service.name === 'RabbitMQ' && !$scope.rabbitmqBranchChosen)
+ || (service.name === 'Elasticsearch' && !$scope.esMajorChosen);
+ if (!deferVersionList) {
+ var svcVers = sanitizeVersionsArray(service.versions || []);
+ if (svcVers.length > 0) {
+ $scope.selectedVersions = ['latest'].concat(svcVers);
+ }
+ var curPick = normalizeVersionToken(effectiveInstalled) || effectiveInstalled;
+ if (curPick && $scope.selectedVersions.indexOf(curPick) === -1) {
+ $scope.selectedVersions.push(curPick);
+ }
+ }
+ $scope.selectedVersion = 'latest';
+ $scope.requestData = '';
+
+ var realVers = ($scope.selectedVersions || []).filter(function (v) {
+ return v && v !== 'latest';
+ });
+ if (deferVersionList) {
+ $scope.repoShowsOnlyOneStream = false;
+ } else {
+ $scope.repoShowsOnlyOneStream = ($scope.status === 'Upgrading' && realVers.length <= 1);
+ }
+ $scope.recalcSelectedVersionRowIndex();
+ };
+
+ $scope.findAppMeta = function (appName) {
+ var found = null;
+ ($scope.apps || []).forEach(function (item) {
+ if (item.name === appName) {
+ found = item;
+ }
+ });
+ return found || {};
+ };
+
+ $scope.prepareActionByName = function (appName, status, bootstrapInstalledVersion) {
+ var meta = $scope.findAppMeta(appName);
+ if (!meta.name) {
+ meta = {name: appName, versions: []};
+ }
+ var mver = meta.versions;
+ if (!angular.isArray(mver)) {
+ mver = [];
+ }
+ var merged = {
+ name: meta.name,
+ image: meta.image,
+ installed: meta.installed,
+ installedVersion: meta.installedVersion || '',
+ versions: mver
+ };
+ $scope.prepareAction(merged, status, bootstrapInstalledVersion);
+ };
+
+ /**
+ * Prepare scope then show modal (do not use data-toggle + ng-click — Bootstrap can
+ * open the dialog before Angular runs ng-click, leaving status/appName unset).
+ * For managed apps, wait for applicationMeta so version lists and installedVersion are correct (upgrade vs downgrade).
+ */
+ $scope.openApplicationsModal = function (appName, status, bootstrapInstalledVersion) {
+ var needMeta = (appName === 'RabbitMQ' || appName === 'Elasticsearch' || appName === 'Redis');
+ var showModal = function () {
+ if (typeof window.jQuery !== 'undefined' && jQuery('#settings').modal) {
+ jQuery('#settings').modal('show');
+ }
+ };
+ if (needMeta) {
+ if (appName === 'RabbitMQ') {
+ if (status === 'Installing') {
+ $scope.selectedRabbitmqStream = '4';
+ $scope.rabbitmqBranchChosen = true;
+ } else {
+ $scope.rabbitmqBranchChosen = false;
+ }
+ }
+ if (appName === 'Elasticsearch') {
+ $scope.esMajorChosen = false;
+ }
+ // RabbitMQ install: default 4.x stream and prefetch metadata. Upgrade: pick stream from installed version.
+ // Elasticsearch: still wait for user major. Redis refreshes on open.
+ $scope.appName = appName;
+ $scope.status = status;
+ $scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
+ showModal();
+ if (appName === 'Redis' || (appName === 'RabbitMQ' && $scope.rabbitmqBranchChosen)) {
+ $timeout(function () {
+ $scope.refreshMeta();
+ }, 0);
+ }
+ } else {
+ $scope.prepareActionByName(appName, status, bootstrapInstalledVersion);
+ showModal();
+ }
+ };
+
+ $scope.runAction = function () {
+ var appName = $scope.appName;
+ var status = $scope.status;
+ if (!appName || !status) {
+ return;
+ }
+ if ((status === 'Removing' || status === 'Upgrading') && !$scope.confirmAction) {
+ new PNotify({
+ title: 'Confirmation Required',
+ text: 'Please confirm this action before proceeding.',
+ type: 'warning'
+ });
+ return;
+ }
+ if (appName === 'RabbitMQ' && (status === 'Installing' || status === 'Upgrading') && !$scope.rabbitmqBranchChosen) {
+ new PNotify({
+ title: 'Stream required',
+ text: 'Choose RabbitMQ 4.x or 3.x above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
+ if (appName === 'Elasticsearch' && (status === 'Installing' || status === 'Upgrading') && !$scope.esMajorChosen) {
+ new PNotify({
+ title: 'Major version required',
+ text: 'Choose Elasticsearch major (7, 8, or 9) above to load versions for that line.',
+ type: 'warning'
+ });
+ return;
+ }
$scope.status = status;
$scope.appName = appName;
@@ -441,7 +934,11 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
var data = {
appName: appName,
- status: status
+ status: status,
+ version: $scope.selectedVersion || 'latest',
+ esMajor: $scope.selectedEsMajor || '8',
+ rabbitmqStream: $scope.selectedRabbitmqStream || '4',
+ confirmAction: $scope.confirmAction === true
};
var config = {
@@ -524,6 +1021,18 @@ app.controller('manageApplications', function ($scope, $http, $timeout, $window)
}
+ // Do not fetch package metadata on page load; it can block workers under DNF load.
+ // Metadata is fetched on-demand when opening install/version-change modals.
+
+ if (typeof window.jQuery !== 'undefined' && jQuery.fn.on) {
+ jQuery('#settings').on('hidden.bs.modal', function () {
+ $scope.$evalAsync(function () {
+ $scope.rabbitmqBranchChosen = false;
+ $scope.esMajorChosen = false;
+ });
+ });
+ }
+
});
/* Java script code */
\ No newline at end of file