open Manage straight to the list and simplify registry rows

This commit is contained in:
MacRimi
2026-10-17 21:49:00 +02:00
parent 750421a12f
commit d8ae19c76e
6 changed files with 65 additions and 9 deletions
@@ -42,13 +42,13 @@ class SelectionSetupWording(TestCase):
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: [],
_clean_orphans=lambda project: None,
recover_automatically=lambda project: None, offer_recovery=lambda project, ui: None,
carry_records=lambda project, **options: {},
carry_records=lambda project, **options: {}, _records_unread=lambda project: False,
translate=lambda s: s)
fn(Path('/fixture'), ui)
ui.message.assert_called_once_with(EMPTY, 'OCI management')
ui.choose.assert_not_called()
def _menu(self, inventories, selections):
def _menu(self, inventories, selections, unread=True):
rows = iter(inventories)
ui = SimpleNamespace(message=Mock(), choose=Mock(side_effect=selections))
refresh = Mock(return_value={})
@@ -57,7 +57,8 @@ class SelectionSetupWording(TestCase):
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: next(rows),
_clean_orphans=lambda project: None,
recover_automatically=lambda project: None, offer_recovery=lambda project, ui: None,
carry_records=refresh, manage_instance=manage, menu_size=lambda: 10,
carry_records=refresh, _records_unread=lambda project: unread,
manage_instance=manage, menu_size=lambda: 10,
STATUS_LABELS={}, translate=lambda s: s)
fn(Path('/fixture'), ui)
return ui, refresh, manage
@@ -67,7 +68,14 @@ class SelectionSetupWording(TestCase):
ui, refresh, manage = self._menu([[row], [row]], ['101', None])
self.assertEqual(ui.choose.call_count, 2)
manage.assert_called_once()
refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=True)
refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=True,
heading='Manage installed OCI applications')
def test_the_list_opens_without_the_sync_notice_once_every_configuration_is_cached(self):
row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'}
_, refresh, _ = self._menu([[row]], [None], unread=False)
refresh.assert_called_once_with(Path('/fixture'), mount_stopped=False, shown=False,
heading='Manage installed OCI applications')
def test_removing_the_last_application_returns_without_a_message(self):
row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'}
@@ -50,6 +50,7 @@ jobs:
PYTHONDONTWRITEBYTECODE: '1'
run: |
python3 -m unittest discover -s oci/tests -p test_modify_menu.py -v
python3 -m unittest discover -s oci/tests -p test_manage_sync_notice.py -v
python3 -m unittest discover -s oci/tests -p test_recreation.py -v
python3 -m unittest discover -s oci/tests -p test_shared_directories.py -v
python3 -m unittest discover -s oci/tests -p 'test_wizard*.py' -v
-2
View File
@@ -578,8 +578,6 @@ def interactive(catalog: Catalog) -> int:
f"{translate('All applications')} ({len(applications)})")
elif selection == "manage":
from .management import interactive_management
console.show_logo()
console.msg_title(translate("Manage installed OCI applications"))
interactive_management(PROJECT_ROOT, ui)
elif selection == "registries":
from .registry_accounts import open_registry_accounts
+17 -2
View File
@@ -132,7 +132,7 @@ def _run_lifecycle(command, title):
return completed.returncode == 0
def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=False):
def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=False, heading=None):
"""Leave inside each container the copy of its record that a restore on
another host needs. It is refreshed after every operation. Returns what
was found for each container. Every installed application is looked at,
@@ -140,6 +140,9 @@ def carry_records(project, mount_stopped=True, vmids=None, verify=False, shown=F
from . import console
sys.path.insert(0, str(project / 'remote'))
if shown:
if heading:
console.show_logo()
console.msg_title(heading)
console.msg_info(translate('Updating the copy of the record...'))
try:
import oci_carried_record
@@ -234,6 +237,17 @@ def interactive_management(project, ui):
ui.message(translate('OCI management could not be completed. Check the backend status; no additional cleanup has been authorized.'), translate('OCI management'))
def _records_unread(project):
"""Whether an installed application has no cached configuration yet; only
then does the first sync read every container and take a while."""
sys.path.insert(0, str(project / 'remote'))
import oci_carried_record as carried
return any(not (carried.instances.location(carried.instances.ROOT, row['vmid']).parent
/ carried.CONFIG_CACHE).exists()
for row in saved_inventory(project)
if row.get('status') == 'installed' and not row.get('pending'))
def _clean_orphans(project):
"""Remove, silently, what is left of containers that exist on no node of the cluster."""
sys.path.insert(0, str(project / 'remote'))
@@ -253,7 +267,8 @@ def _interactive_management(project, ui):
_clean_orphans(project)
recover_automatically(project)
offer_recovery(project, ui)
carry_records(project, mount_stopped=False, shown=True)
carry_records(project, mount_stopped=False, shown=_records_unread(project),
heading=translate('Manage installed OCI applications'))
# After each operation the list is shown again. Every operation leaves
# the copies of the records up to date, so they are not all refreshed again.
first = True
+1 -1
View File
@@ -166,7 +166,7 @@ def registry_accounts_menu(ui, engine=ENGINE_ROOT, auth=None):
return
try:
options = [('add', translate('Sign in to a registry'))]
options += [(f'account-{index}', f"{row['registry']:<28} {row['account'] or row['method']} [{translate('saved')}]")
options += [(f'account-{index}', f"{row['registry']:<28} [{translate('saved')}]")
for index, row in enumerate(rows)]
options += [('storage', translate('Credential storage and access'))]
count = translate('{count} saved account.') if len(rows) == 1 else translate('{count} saved accounts.')
+34
View File
@@ -0,0 +1,34 @@
"""Opening Manage shows the sync notice only while some configuration is still unread."""
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'src'))
sys.path.insert(0, str(ROOT / 'remote'))
from proxmenux_oci import management
import oci_carried_record as carried
class ManageSyncNoticeTests(unittest.TestCase):
def test_notice_only_while_an_installed_application_has_no_cache(self):
rows = [{'vmid': 101, 'status': 'installed', 'pending': False},
{'vmid': 102, 'status': 'installed', 'pending': True},
{'vmid': 103, 'status': 'failed', 'pending': False}]
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
with patch.object(carried.instances, 'ROOT', root), \
patch.object(management, 'saved_inventory', return_value=rows):
self.assertTrue(management._records_unread(ROOT))
cache = carried.instances.location(root, 101).parent / carried.CONFIG_CACHE
cache.parent.mkdir(parents=True)
cache.write_text('{}')
# The pending and failed applications are never cached by the sync.
self.assertFalse(management._records_unread(ROOT))
if __name__ == '__main__':
unittest.main()