mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
open Manage straight to the list and simplify registry rows
This commit is contained in:
@@ -42,13 +42,13 @@ class SelectionSetupWording(TestCase):
|
|||||||
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: [],
|
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: [],
|
||||||
_clean_orphans=lambda project: None,
|
_clean_orphans=lambda project: None,
|
||||||
recover_automatically=lambda project: None, offer_recovery=lambda project, ui: 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)
|
translate=lambda s: s)
|
||||||
fn(Path('/fixture'), ui)
|
fn(Path('/fixture'), ui)
|
||||||
ui.message.assert_called_once_with(EMPTY, 'OCI management')
|
ui.message.assert_called_once_with(EMPTY, 'OCI management')
|
||||||
ui.choose.assert_not_called()
|
ui.choose.assert_not_called()
|
||||||
|
|
||||||
def _menu(self, inventories, selections):
|
def _menu(self, inventories, selections, unread=True):
|
||||||
rows = iter(inventories)
|
rows = iter(inventories)
|
||||||
ui = SimpleNamespace(message=Mock(), choose=Mock(side_effect=selections))
|
ui = SimpleNamespace(message=Mock(), choose=Mock(side_effect=selections))
|
||||||
refresh = Mock(return_value={})
|
refresh = Mock(return_value={})
|
||||||
@@ -57,7 +57,8 @@ class SelectionSetupWording(TestCase):
|
|||||||
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: next(rows),
|
shutil=SimpleNamespace(which=lambda _: '/fake/pct'), saved_inventory=lambda _: next(rows),
|
||||||
_clean_orphans=lambda project: None,
|
_clean_orphans=lambda project: None,
|
||||||
recover_automatically=lambda project: None, offer_recovery=lambda project, ui: 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)
|
STATUS_LABELS={}, translate=lambda s: s)
|
||||||
fn(Path('/fixture'), ui)
|
fn(Path('/fixture'), ui)
|
||||||
return ui, refresh, manage
|
return ui, refresh, manage
|
||||||
@@ -67,7 +68,14 @@ class SelectionSetupWording(TestCase):
|
|||||||
ui, refresh, manage = self._menu([[row], [row]], ['101', None])
|
ui, refresh, manage = self._menu([[row], [row]], ['101', None])
|
||||||
self.assertEqual(ui.choose.call_count, 2)
|
self.assertEqual(ui.choose.call_count, 2)
|
||||||
manage.assert_called_once()
|
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):
|
def test_removing_the_last_application_returns_without_a_message(self):
|
||||||
row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'}
|
row = {'vmid': 101, 'title': 'Jellyfin', 'hostname': 'jellyfin', 'status': 'installed', 'image': 'jellyfin/jellyfin'}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ jobs:
|
|||||||
PYTHONDONTWRITEBYTECODE: '1'
|
PYTHONDONTWRITEBYTECODE: '1'
|
||||||
run: |
|
run: |
|
||||||
python3 -m unittest discover -s oci/tests -p test_modify_menu.py -v
|
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_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_shared_directories.py -v
|
||||||
python3 -m unittest discover -s oci/tests -p 'test_wizard*.py' -v
|
python3 -m unittest discover -s oci/tests -p 'test_wizard*.py' -v
|
||||||
|
|||||||
@@ -578,8 +578,6 @@ def interactive(catalog: Catalog) -> int:
|
|||||||
f"{translate('All applications')} ({len(applications)})")
|
f"{translate('All applications')} ({len(applications)})")
|
||||||
elif selection == "manage":
|
elif selection == "manage":
|
||||||
from .management import interactive_management
|
from .management import interactive_management
|
||||||
console.show_logo()
|
|
||||||
console.msg_title(translate("Manage installed OCI applications"))
|
|
||||||
interactive_management(PROJECT_ROOT, ui)
|
interactive_management(PROJECT_ROOT, ui)
|
||||||
elif selection == "registries":
|
elif selection == "registries":
|
||||||
from .registry_accounts import open_registry_accounts
|
from .registry_accounts import open_registry_accounts
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ def _run_lifecycle(command, title):
|
|||||||
return completed.returncode == 0
|
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
|
"""Leave inside each container the copy of its record that a restore on
|
||||||
another host needs. It is refreshed after every operation. Returns what
|
another host needs. It is refreshed after every operation. Returns what
|
||||||
was found for each container. Every installed application is looked at,
|
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
|
from . import console
|
||||||
sys.path.insert(0, str(project / 'remote'))
|
sys.path.insert(0, str(project / 'remote'))
|
||||||
if shown:
|
if shown:
|
||||||
|
if heading:
|
||||||
|
console.show_logo()
|
||||||
|
console.msg_title(heading)
|
||||||
console.msg_info(translate('Updating the copy of the record...'))
|
console.msg_info(translate('Updating the copy of the record...'))
|
||||||
try:
|
try:
|
||||||
import oci_carried_record
|
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'))
|
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):
|
def _clean_orphans(project):
|
||||||
"""Remove, silently, what is left of containers that exist on no node of the cluster."""
|
"""Remove, silently, what is left of containers that exist on no node of the cluster."""
|
||||||
sys.path.insert(0, str(project / 'remote'))
|
sys.path.insert(0, str(project / 'remote'))
|
||||||
@@ -253,7 +267,8 @@ def _interactive_management(project, ui):
|
|||||||
_clean_orphans(project)
|
_clean_orphans(project)
|
||||||
recover_automatically(project)
|
recover_automatically(project)
|
||||||
offer_recovery(project, ui)
|
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
|
# 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.
|
# the copies of the records up to date, so they are not all refreshed again.
|
||||||
first = True
|
first = True
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ def registry_accounts_menu(ui, engine=ENGINE_ROOT, auth=None):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
options = [('add', translate('Sign in to a registry'))]
|
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)]
|
for index, row in enumerate(rows)]
|
||||||
options += [('storage', translate('Credential storage and access'))]
|
options += [('storage', translate('Credential storage and access'))]
|
||||||
count = translate('{count} saved account.') if len(rows) == 1 else translate('{count} saved accounts.')
|
count = translate('{count} saved account.') if len(rows) == 1 else translate('{count} saved accounts.')
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user