diff --git a/home/app_manager.py b/home/app_manager.py index a6a5ccc..c0ffbbb 100644 --- a/home/app_manager.py +++ b/home/app_manager.py @@ -5,6 +5,7 @@ import ipywidgets as ipw import traitlets +from aiidalab.app import AppRemoteUpdateStatus as AppStatus from aiidalab.app import AppVersion from jinja2 import Template from packaging.version import parse @@ -240,6 +241,10 @@ def _refresh_widget_state(self, _=None): installed = self.app.is_installed() installed_version = self.app.installed_version compatible = len(self.app.available_versions) > 0 + registered = self.app.remote_update_status is not AppStatus.NOT_REGISTERED + cannot_reach_registry = ( + self.app.remote_update_status is AppStatus.CANNOT_REACH_REGISTRY + ) busy = self.app.busy detached = self.app.detached available_versions = self.app.available_versions @@ -248,7 +253,9 @@ def _refresh_widget_state(self, _=None): blocked_install = ( detached or not compatible ) and not self.blocked_ignore.value - blocked_uninstall = detached and not self.blocked_ignore.value + blocked_uninstall = ( + detached or not registered or cannot_reach_registry + ) and not self.blocked_ignore.value # Check app compatibility and show banner if not compatible. self.compatibility_warning.layout.visibility = ( @@ -261,11 +268,9 @@ def _refresh_widget_state(self, _=None): # These messages and icons are only shown if needed. warn_or_ban_icon = "warning" if override else "ban" if override: - tooltip_danger = ( - "Operation will lead to potential loss of local modifications!" - ) + tooltip_danger = "Operation will lead to potential loss of local data!" else: - tooltip_danger = "Operation blocked due to local modifications." + tooltip_danger = "Operation blocked due to potential data loss." tooltip_incompatible = "The app is not supported for this environment." # Determine whether we can install, updated, and uninstall. @@ -279,7 +284,10 @@ def _refresh_widget_state(self, _=None): ) or not installed can_uninstall = installed try: - can_update = self.app.updates_available and installed + can_update = ( + self.app.remote_update_status is AppStatus.UPDATE_AVAILABLE + and installed + ) except RuntimeError: can_update = None @@ -362,10 +370,14 @@ def _refresh_widget_state(self, _=None): ) # Indicate whether there are local modifications and present option for user override. - if detached: + if cannot_reach_registry: + self.issue_indicator.value = f' Unable to reach the registry server.' + elif not registered: + self.issue_indicator.value = f' The app is not registered.' + elif detached: self.issue_indicator.value = ( - f' The app is modified or the installed version ' - "is not on the specified release line." + f' The app has local modifications or was checked out ' + "to an unknown version." ) elif not compatible: self.issue_indicator.value = f' The app is not supported for this environment.' diff --git a/home/app_store.py b/home/app_store.py index 17ab85b..58aaf61 100644 --- a/home/app_store.py +++ b/home/app_store.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """AiiDAlab app store.""" +import logging import ipywidgets as ipw from aiidalab.app import AiidaLabApp @@ -9,13 +10,18 @@ from home.app_manager import AppManagerWidget +logger = logging.getLogger(__name__) + class AiidaLabAppStore(ipw.HBox): """Class to manage AiiDAlab app store.""" def __init__(self): - # TODO: Improve fallback implementation! - self.index = load_app_registry_index() or dict(apps=[], categories=[]) + try: + self.index = load_app_registry_index() + except RuntimeError as error: + logger.warning(error) + self.index = dict(apps=[], categories=[]) self.output = ipw.Output() # Apps per page. diff --git a/home/start_page.py b/home/start_page.py index 1b02fdc..b58ce97 100644 --- a/home/start_page.py +++ b/home/start_page.py @@ -153,7 +153,7 @@ def __init__(self, app, allow_move=False, allow_manage=True): if allow_manage: app_status_info = AppStatusInfoWidget() - for trait in ("detached", "compatible", "updates_available"): + for trait in ("detached", "compatible", "remote_update_status"): ipw.dlink((app, trait), (app_status_info, trait)) app_status_info.layout.margin = "0px 0px 0px 800px" header_items.append(app_status_info) diff --git a/home/themes.py b/home/themes.py index 6690555..d97dd86 100644 --- a/home/themes.py +++ b/home/themes.py @@ -7,11 +7,13 @@ class IconSetDefault: CHAIN_BROKEN = '' LOADING = '' ARROW_CIRCLE_UP = '' + FOLDER = '' # App states (general) APP_DETACHED = CHAIN_BROKEN APP_INCOMPATIBLE = TIMES_CIRCLE APP_VERSION_INCOMPATIBLE = WARNING + APP_NOT_REGISTERED = FOLDER # App states (updates) APP_NO_UPDATE_AVAILABLE = CHECK diff --git a/home/widgets.py b/home/widgets.py index 5b0fa32..e08d133 100644 --- a/home/widgets.py +++ b/home/widgets.py @@ -5,6 +5,8 @@ import ipywidgets as ipw import traitlets +from aiidalab.app import AppRemoteUpdateStatus as AppStatus +from aiidalab.config import AIIDALAB_REGISTRY from .themes import ThemeDefault as Theme @@ -74,26 +76,23 @@ class AppStatusInfoWidget(ipw.HTML): detached = traitlets.Bool(allow_none=True) compatible = traitlets.Bool(allow_none=True) - updates_available = traitlets.Bool(allow_none=True) + remote_update_status = traitlets.UseEnum(AppStatus) MESSAGE_INIT = f"