diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index f4470f5a9..633c8484d 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -213,7 +213,9 @@ def _update_health() -> None: coroutine=_health_load_and_render(), name="_health_load_and_render", ) - ui.run_javascript("document.getElementById('betterstack').src = document.getElementById('betterstack').src;") + # Only refresh the status iframe if it exists (production/staging) + if settings().status_page_url: + ui.run_javascript("document.getElementById('betterstack').src = document.getElementById('betterstack').src;") ui.timer(interval=HEALTH_UPDATE_INTERVAL, callback=_update_health, immediate=True) @@ -342,13 +344,15 @@ def toggle_dark_mode() -> None: ui.link("Get Support", "https://platform.aignostics.com/support", new_tab=True).mark( "LINK_DOCUMENTATION" ) - with ui.item().props("clickable"): - with ui.item_section().props("avatar"): - ui.icon("check_circle", color="primary") - with ui.item_section(): - ui.link("Check Platform Status", "https://status.aignostics.com", new_tab=True).mark( - "LINK_DOCUMENTATION" - ) + status_url = settings().status_page_url + if status_url: + with ui.item().props("clickable"): + with ui.item_section().props("avatar"): + ui.icon("check_circle", color="primary") + with ui.item_section(): + ui.link("Check Platform Status", status_url, new_tab=True).mark( + "LINK_DOCUMENTATION" + ) with ui.item().props("clickable"): with ui.item_section().props("avatar"): ui.icon("handshake", color="primary") @@ -368,14 +372,16 @@ def toggle_dark_mode() -> None: ui.row(align_items="center").classes("justify-start w-full"), ): health_link() - with ui.row().style("padding: 0"): - ui.html( - '', - sanitize=False, - ).style("margin-left: 0px;") - ui.tooltip("Check Platform Status") + status_url = settings().status_page_url + if status_url: + with ui.row().style("padding: 0"): + ui.html( + f'', + sanitize=False, + ).style("margin-left: 0px;") + ui.tooltip("Check Platform Status") ui.space() with ui.row(): flavor = " (native)" if getattr(sys, "frozen", False) else "" diff --git a/src/aignostics/platform/__init__.py b/src/aignostics/platform/__init__.py index 4a9d28efd..9fca31b16 100644 --- a/src/aignostics/platform/__init__.py +++ b/src/aignostics/platform/__init__.py @@ -71,6 +71,10 @@ REDIRECT_URI_TEST, REDIRECT_URI_STAGING, REDIRECT_URI_PRODUCTION, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_TEST, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_PRODUCTION, TOKEN_URL_DEV, TOKEN_URL_TEST, TOKEN_URL_STAGING, diff --git a/src/aignostics/platform/_constants.py b/src/aignostics/platform/_constants.py index c64315b44..8e9be78b2 100644 --- a/src/aignostics/platform/_constants.py +++ b/src/aignostics/platform/_constants.py @@ -8,6 +8,7 @@ REDIRECT_URI_DEV = "http://localhost:8989/" DEVICE_URL_DEV = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/device/code" JWS_JSON_URL_DEV = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_DEV = None # No dedicated status page for dev environment API_ROOT_TEST = "https://platform-test.aignostics.ai" CLIENT_ID_INTERACTIVE_TEST = "gqduveFvx7LX90drQPGzr4JGUYdh24gA" # not a secret, but a public client ID (same as dev) @@ -17,6 +18,7 @@ REDIRECT_URI_TEST = "http://localhost:8989/" DEVICE_URL_TEST = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/device/code" JWS_JSON_URL_TEST = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_TEST = None # No dedicated status page for test environment API_ROOT_STAGING = "https://platform-staging.aignostics.com" CLIENT_ID_INTERACTIVE_STAGING = "fQkbvYzQPPVwLxc3uque5JsyFW00rJ7b" # not a secret, but a public client ID @@ -26,6 +28,7 @@ REDIRECT_URI_STAGING = "http://localhost:8989/" DEVICE_URL_STAGING = "https://aignostics-platform-staging.eu.auth0.com/oauth/device/code" JWS_JSON_URL_STAGING = "https://aignostics-platform-staging.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_STAGING = "https://status.platform-staging.aignostics.com" API_ROOT_PRODUCTION = "https://platform.aignostics.com" CLIENT_ID_INTERACTIVE_PRODUCTION = "YtJ7F9lAtxx16SZGQlYPe6wcjlXB78MM" # not a secret, but a public client ID @@ -35,6 +38,7 @@ REDIRECT_URI_PRODUCTION = "http://localhost:8989/" DEVICE_URL_PRODUCTION = "https://aignostics-platform.eu.auth0.com/oauth/device/code" JWS_JSON_URL_PRODUCTION = "https://aignostics-platform.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_PRODUCTION = "https://status.platform.aignostics.com" # Pipeline orchestration defaults DEFAULT_GPU_TYPE = "L4" diff --git a/src/aignostics/platform/_settings.py b/src/aignostics/platform/_settings.py index 71ca1d3a5..cf565466c 100644 --- a/src/aignostics/platform/_settings.py +++ b/src/aignostics/platform/_settings.py @@ -50,6 +50,10 @@ REDIRECT_URI_TEST, REDIRECT_URI_STAGING, REDIRECT_URI_PRODUCTION, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_TEST, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_PRODUCTION, TOKEN_URL_DEV, TOKEN_URL_TEST, TOKEN_URL_STAGING, @@ -205,6 +209,9 @@ def profile_edit_url(self) -> str: str, BeforeValidator(_validate_url), Field(description="JWS key set URL for token verification") ] client_id_interactive: Annotated[str, Field(description="OAuth client ID for interactive flows")] + status_page_url: Annotated[ + str | None, Field(description="Status page URL for monitoring platform health", default=None) + ] = None @computed_field # type: ignore[prop-decorator] @property @@ -510,6 +517,20 @@ def pre_init(cls, values: dict) -> dict: # type: ignore[type-arg] # noqa: N805 # See https://github.com/pydantic/pydantic/issues/9789 api_root = values.get("api_root", API_ROOT_PRODUCTION) + # Set status_page_url based on environment (independent of auth fields) + if "status_page_url" not in values: + match api_root: + case x if x == API_ROOT_DEV: + values["status_page_url"] = STATUS_PAGE_URL_DEV + case x if x == API_ROOT_TEST: + values["status_page_url"] = STATUS_PAGE_URL_TEST + case x if x == API_ROOT_STAGING: + values["status_page_url"] = STATUS_PAGE_URL_STAGING + case x if x == API_ROOT_PRODUCTION: + values["status_page_url"] = STATUS_PAGE_URL_PRODUCTION + case _: + values["status_page_url"] = None + # Check if all required auth fields are already provided auth_fields = [ "audience", diff --git a/tests/aignostics/gui/__init__.py b/tests/aignostics/gui/__init__.py new file mode 100644 index 000000000..ff8b2654f --- /dev/null +++ b/tests/aignostics/gui/__init__.py @@ -0,0 +1 @@ +"""Tests for GUI module.""" diff --git a/tests/aignostics/gui/frame_test.py b/tests/aignostics/gui/frame_test.py new file mode 100644 index 000000000..15f010b5e --- /dev/null +++ b/tests/aignostics/gui/frame_test.py @@ -0,0 +1,84 @@ +"""Tests for GUI frame module.""" + +import pytest + +from aignostics.platform import ( + API_ROOT_DEV, + API_ROOT_PRODUCTION, + API_ROOT_STAGING, + API_ROOT_TEST, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_PRODUCTION, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_TEST, + Settings, +) + + +@pytest.mark.unit +def test_status_page_url_production(record_property) -> None: + """Test that production environment has correct status page URL in settings. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_PRODUCTION) + assert settings.status_page_url == STATUS_PAGE_URL_PRODUCTION + assert settings.status_page_url == "https://status.platform.aignostics.com" + + +@pytest.mark.unit +def test_status_page_url_staging(record_property) -> None: + """Test that staging environment has correct status page URL in settings. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_STAGING) + assert settings.status_page_url == STATUS_PAGE_URL_STAGING + assert settings.status_page_url == "https://status.platform-staging.aignostics.com" + + +@pytest.mark.unit +def test_status_page_url_dev(record_property) -> None: + """Test that dev environment has no status page URL in settings. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_DEV) + assert settings.status_page_url == STATUS_PAGE_URL_DEV + assert settings.status_page_url is None + + +@pytest.mark.unit +def test_status_page_url_test(record_property) -> None: + """Test that test environment has no status page URL in settings. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_TEST) + assert settings.status_page_url == STATUS_PAGE_URL_TEST + assert settings.status_page_url is None + + +@pytest.mark.unit +def test_status_page_url_configurable(record_property, monkeypatch) -> None: + """Test that status page URL can be explicitly configured via settings. + + Args: + record_property: pytest record_property fixture + monkeypatch: pytest monkeypatch fixture + """ + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + # Test that we can override the status page URL + custom_url = "https://custom-status.example.com" + monkeypatch.setenv("AIGNOSTICS_STATUS_PAGE_URL", custom_url) + settings = Settings(api_root=API_ROOT_PRODUCTION) + assert settings.status_page_url == custom_url +