Manage Services: RabbitMQ 4.x default, repo alignment, UI fixes

- Detect RHEL major from /etc/os-release and align Packagecloud RabbitMQ .repo URLs.
- Improve version discovery (el8 metadata merge on EL9+, 4.x fallback when DNF omits builds).
- Default RabbitMQ stream to 4.x in API, page bootstrap, serviceManager, and normalize_rabbitmq_stream.
- UI: prefetch 4.x on install, stream buttons 4.x first, fix confirm checkbox ng-model parent scope.
- Bump msModal cache-bust for manageServices.js.
This commit is contained in:
master3395
2026-04-01 03:27:17 +02:00
parent 82ec34f339
commit 774c72f159
11 changed files with 1261 additions and 166 deletions

View File

@@ -2504,7 +2504,7 @@
<script src="{% static 'serverStatus/serverStatus.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}&fw={{ FIREWALL_STATIC_VERSION|default:CP_VERSION }}&cb=4" data-cfasync="false"></script>
<script src="{% static 'emailPremium/emailPremium.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'manageServices/manageServices.js' %}?v={{ CP_VERSION }}&msModal=20260401d" data-cfasync="false"></script>
<script src="{% static 'manageServices/manageServices.js' %}?v={{ CP_VERSION }}&msModal=20260402c" data-cfasync="false"></script>
<script src="{% static 'CLManager/CLManager.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<!-- Scripts -->

View File

@@ -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 = ''

View File

@@ -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
)

View File

@@ -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-<version> (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.')

View File

@@ -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.

View File

@@ -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':

View File

@@ -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 */

View File

@@ -587,7 +587,44 @@
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div style="margin-bottom: 10px;" ng-if="status == 'Installing' || status == 'Upgrading'">
<div style="margin-bottom: 10px;" ng-if="appName == 'Elasticsearch' && (status == 'Installing' || status == 'Upgrading')">
<label style="color: #0f172a;">{% trans "Elasticsearch Major" %}</label>
<div class="manage-apps-version-picker">
<div class="manage-apps-version-current">{% trans "Major" %}:
<span ng-if="esMajorChosen" ng-bind="selectedEsMajor"></span>
<span ng-if="!esMajorChosen">{% trans "Select a major below — versions load after you choose." %}</span>
</div>
<div class="manage-apps-version-rows">
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': esMajorChosen && selectedEsMajor === '7'}" ng-click="chooseEsMajor('7')">7</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': esMajorChosen && selectedEsMajor === '8'}" ng-click="chooseEsMajor('8')">8</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': esMajorChosen && selectedEsMajor === '9'}" ng-click="chooseEsMajor('9')">9</button>
</div>
</div>
</div>
<div style="margin-bottom: 10px;" ng-if="appName == 'RabbitMQ' && (status == 'Installing' || status == 'Upgrading')">
<label style="color: #0f172a;">{% trans "RabbitMQ major line" %}</label>
<div class="manage-apps-version-picker">
<div class="manage-apps-version-current">{% trans "Stream" %}:
<span ng-if="rabbitmqBranchChosen && selectedRabbitmqStream === '4'">4.x</span>
<span ng-if="rabbitmqBranchChosen && selectedRabbitmqStream === '3'">3.x ({% trans "maintenance" %})</span>
<span ng-if="!rabbitmqBranchChosen">{% trans "Select a stream below — versions load after you choose." %}</span>
</div>
<div class="manage-apps-version-rows">
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': rabbitmqBranchChosen && selectedRabbitmqStream === '4'}" ng-click="chooseRabbitmqStream('4')">4.x</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': rabbitmqBranchChosen && selectedRabbitmqStream === '3'}" ng-click="chooseRabbitmqStream('3')">3.x ({% trans "maintenance" %})</button>
</div>
</div>
<p style="margin-top: 8px; font-size: 12px; color: #64748b;">
{% 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." %}
</p>
<div style="margin-top: 10px; padding: 10px 12px; background: #fff7ed; border-radius: 8px; font-size: 12px; color: #9a3412; border: 1px solid #fed7aa;"
ng-if="rabbitmqBranchChosen && findAppMeta('RabbitMQ').rabbitmqVersionsHint">
<i class="fas fa-exclamation-triangle"></i>
<span ng-bind="findAppMeta('RabbitMQ').rabbitmqVersionsHint"></span>
</div>
</div>
<div style="margin-bottom: 10px;" ng-if="(status == 'Installing' || status == 'Upgrading') && (appName != 'RabbitMQ' || rabbitmqBranchChosen) && (appName != 'Elasticsearch' || esMajorChosen)">
<label style="color: #0f172a;">{% trans "Version" %}</label>
<div class="manage-apps-version-picker">
<div class="manage-apps-version-current">
@@ -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." %}
</div>
<div style="margin-bottom: 10px;" ng-if="appName == 'Elasticsearch' && (status == 'Installing' || status == 'Upgrading')">
<label style="color: #0f172a;">{% trans "Elasticsearch Major" %}</label>
<div class="manage-apps-version-picker">
<div class="manage-apps-version-current">{% trans "Major" %}: <span ng-bind="selectedEsMajor"></span></div>
<div class="manage-apps-version-rows">
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': selectedEsMajor === '7'}" ng-click="selectedEsMajor = '7'; refreshMeta()">7</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': selectedEsMajor === '8'}" ng-click="selectedEsMajor = '8'; refreshMeta()">8</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': selectedEsMajor === '9'}" ng-click="selectedEsMajor = '9'; refreshMeta()">9</button>
</div>
</div>
</div>
<div style="margin-bottom: 10px;" ng-if="appName == 'RabbitMQ' && (status == 'Installing' || status == 'Upgrading')">
<label style="color: #0f172a;">{% trans "RabbitMQ major line" %}</label>
<div class="manage-apps-version-picker">
<div class="manage-apps-version-current">{% trans "Stream" %}: <span ng-bind="selectedRabbitmqStream === '4' ? '4.x' : '3.x'"></span></div>
<div class="manage-apps-version-rows">
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': selectedRabbitmqStream === '3'}" ng-click="selectedRabbitmqStream = '3'; refreshMeta()">3.x ({% trans "maintenance" %})</button>
<button type="button" class="manage-apps-version-row" ng-class="{'is-active': selectedRabbitmqStream === '4'}" ng-click="selectedRabbitmqStream = '4'; refreshMeta()">4.x</button>
</div>
</div>
<p style="margin-top: 8px; font-size: 12px; color: #64748b;">
{% trans "Uses Team RabbitMQ Packagecloud repos; 4.x requires a compatible Erlang (OTP 26+). The installer will try to align Erlang when needed." %}
</p>
</div>
<div style="margin-bottom: 10px; padding: 10px 12px; background: #f0f9ff; border-radius: 8px; font-size: 13px; color: #1e3a5f; border: 1px solid #bae6fd;"
ng-if="(status == 'Installing' || status == 'Upgrading') && findAppMeta(appName).crossBranchUpdateSuggested">
<i class="fas fa-info-circle"></i>
@@ -647,7 +660,8 @@
</div>
<div style="margin-bottom: 10px;" ng-if="status == 'Removing' || status == 'Upgrading'">
<label>
<input type="checkbox" ng-model="confirmAction">
{# ng-if creates a child scope; bind to parent so runAction() sees the same flag #}
<input type="checkbox" ng-model="$parent.confirmAction">
<span ng-if="status == 'Removing'">{% trans "Confirm Remove" %}</span>
<span ng-if="status == 'Upgrading'">{% trans "Confirm version change" %}</span>
</label>

View File

@@ -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()

View File

@@ -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 */

View File

@@ -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 */