diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java index f1dadf6890..0e3c19b0ef 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java @@ -1,16 +1,24 @@ package io.apicurio.registry.rest.v3; +import java.util.HashMap; +import java.util.Map; + import io.apicurio.common.apps.core.System; import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.auth.AuthConfig; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; +import io.apicurio.registry.limits.RegistryLimitsConfiguration; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; -import io.apicurio.registry.limits.RegistryLimitsConfiguration; import io.apicurio.registry.rest.v3.beans.Limits; import io.apicurio.registry.rest.v3.beans.SystemInfo; - +import io.apicurio.registry.rest.v3.beans.UserInterfaceConfig; +import io.apicurio.registry.rest.v3.beans.UserInterfaceConfigAuth; +import io.apicurio.registry.rest.v3.beans.UserInterfaceConfigFeatures; +import io.apicurio.registry.rest.v3.beans.UserInterfaceConfigUi; +import io.apicurio.registry.ui.UserInterfaceConfigProperties; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; @@ -22,6 +30,12 @@ public class SystemResourceImpl implements SystemResource { @Inject System system; + + @Inject + AuthConfig authConfig; + + @Inject + UserInterfaceConfigProperties uiConfig; @Inject RegistryLimitsConfiguration registryLimitsConfiguration; @@ -44,6 +58,7 @@ public SystemInfo getSystemInfo() { * @see io.apicurio.registry.rest.v3.SystemResource#getResourceLimits() */ @Override + @Authorized(style=AuthorizedStyle.None, level=AuthorizedLevel.None) public Limits getResourceLimits() { var limitsConfig = registryLimitsConfiguration; var limits = new Limits(); @@ -61,4 +76,41 @@ public Limits getResourceLimits() { limits.setMaxRequestsPerSecondCount(limitsConfig.getMaxRequestsPerSecondCount()); return limits; } + + /** + * @see io.apicurio.registry.rest.v3.SystemResource#getUIConfig() + */ + @Override + @Authorized(style=AuthorizedStyle.None, level=AuthorizedLevel.None) + public UserInterfaceConfig getUIConfig() { + return UserInterfaceConfig.builder() + .ui(UserInterfaceConfigUi.builder() + .contextPath(uiConfig.contextPath) + .navPrefixPath(uiConfig.navPrefixPath) + .oaiDocsUrl(uiConfig.docsUrl) + .build()) + .auth(uiAuthConfig()) + .features(UserInterfaceConfigFeatures.builder() + .readOnly("true".equals(uiConfig.featureReadOnly)) + .breadcrumbs("true".equals(uiConfig.featureBreadcrumbs)) + .roleManagement(authConfig.isRbacEnabled()) + .settings("true".equals(uiConfig.featureSettings)) + .build()) + .build(); + } + + private UserInterfaceConfigAuth uiAuthConfig() { + UserInterfaceConfigAuth rval = new UserInterfaceConfigAuth(); + rval.setObacEnabled(authConfig.isObacEnabled()); + rval.setRbacEnabled(authConfig.isRbacEnabled()); + rval.setType(authConfig.isAuthEnabled() ? UserInterfaceConfigAuth.Type.oidc : UserInterfaceConfigAuth.Type.none); + if (authConfig.isAuthEnabled()) { + Map options = new HashMap<>(); + options.put("url", uiConfig.authOidcUrl); + options.put("redirectUri", uiConfig.authOidcRedirectUri); + options.put("clientId", uiConfig.authOidcClientId); + rval.setOptions(options); + } + return rval; + } } diff --git a/app/src/main/java/io/apicurio/registry/ui/UserInterfaceConfigProperties.java b/app/src/main/java/io/apicurio/registry/ui/UserInterfaceConfigProperties.java new file mode 100644 index 0000000000..1858cb1d95 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/ui/UserInterfaceConfigProperties.java @@ -0,0 +1,42 @@ +package io.apicurio.registry.ui; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.apicurio.common.apps.config.Info; +import jakarta.inject.Singleton; + +@Singleton +public class UserInterfaceConfigProperties { + + @ConfigProperty(name = "registry.ui.contextPath", defaultValue = "/") + @Info(category = "ui", description = "Context path of the UI", availableSince = "3.0.0") + public String contextPath; + @ConfigProperty(name = "registry.ui.navPrefixPath", defaultValue = "/") + @Info(category = "ui", description = "Navigation prefix for all UI paths", availableSince = "3.0.0") + public String navPrefixPath; + @ConfigProperty(name = "registry.ui.docsUrl", defaultValue = "/docs/") + @Info(category = "ui", description = "URL of the Documentation component", availableSince = "3.0.0") + public String docsUrl; + + + @ConfigProperty(name = "registry.auth.url.configured") + public String authOidcUrl; + @ConfigProperty(name = "registry.ui.auth.oidc.redirectUri", defaultValue = "/") + @Info(category = "ui", description = "The OIDC redirectUri", availableSince = "3.0.0") + public String authOidcRedirectUri; + @ConfigProperty(name = "registry.ui.auth.oidc.clientId", defaultValue = "apicurio-registry-ui") + @Info(category = "ui", description = "The OIDC clientId", availableSince = "3.0.0") + public String authOidcClientId; + + + @ConfigProperty(name = "registry.ui.features.readOnly", defaultValue = "false") + @Info(category = "ui", description = "Enabled to set the UI to read-only mode", availableSince = "3.0.0") + public String featureReadOnly; + @ConfigProperty(name = "registry.ui.features.breadcrumbs", defaultValue = "true") + @Info(category = "ui", description = "Enabled to show breadcrumbs in the UI", availableSince = "3.0.0") + public String featureBreadcrumbs; + @ConfigProperty(name = "registry.ui.features.settings", defaultValue = "true") + @Info(category = "ui", description = "Enabled to show the Settings tab in the UI", availableSince = "3.0.0") + public String featureSettings; + +} diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index 74c209878d..3035b3c23a 100644 --- a/common/src/main/resources/META-INF/openapi.json +++ b/common/src/main/resources/META-INF/openapi.json @@ -3380,6 +3380,33 @@ } ] }, + "/system/uiConfig": { + "summary": "Get UI configuration", + "description": "This endpoint is used by the user interface to retrieve UI specific configuration\nin a JSON payload. This allows the UI and the backend to be configured in the \nsame place (the backend process/pod). When the UI loads, it will make an API call\nto this endpoint to determine what UI features and options are configured.", + "get": { + "tags": [ + "System" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserInterfaceConfig" + } + } + }, + "description": "The UI config." + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "operationId": "getUIConfig", + "summary": "Get UI config", + "description": "Returns the UI configuration properties for this server. The registry UI can be\nconnected to a backend using just a URL. The rest of the UI configuration can then\nbe fetched from the backend using this operation. This allows UI and backend to\nboth be configured in the same place.\n\nThis operation may fail for one of the following reasons:\n\n* A server error occurred (HTTP error `500`)\n" + } + }, "x-codegen-contextRoot": "/apis/registry/v3" }, "components": { @@ -4711,6 +4738,182 @@ } ] } + }, + "UserInterfaceConfig": { + "title": "Root Type for UserInterfaceConfig", + "description": "Defines the user interface configuration data type.", + "required": [ + "auth" + ], + "type": "object", + "properties": { + "ui": { + "$ref": "#/components/schemas/UserInterfaceConfigUi", + "properties": { + "contextPath": { + "type": "string" + }, + "navPrefixPath": { + "type": "string" + }, + "oaiDocsUrl": { + "type": "string" + } + } + }, + "auth": { + "$ref": "#/components/schemas/UserInterfaceConfigAuth", + "properties": { + "type": { + "type": "string" + }, + "rbacEnabled": { + "type": "boolean" + }, + "obacEnabled": { + "type": "boolean" + }, + "options": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "redirectUri": { + "type": "string" + }, + "clientId": { + "type": "string" + } + } + } + } + }, + "features": { + "$ref": "#/components/schemas/UserInterfaceConfigFeatures", + "properties": { + "readOnly": { + "type": "boolean" + }, + "breadcrumbs": { + "type": "boolean" + }, + "roleManagement": { + "type": "boolean" + }, + "settings": { + "type": "boolean" + } + } + } + }, + "example": { + "ui": { + "contextPath": "/", + "navPrefixPath": "/", + "oaiDocsUrl": "https://registry.apicur.io/docs" + }, + "auth": { + "type": "oidc", + "rbacEnabled": true, + "obacEnabled": false, + "options": { + "url": "https://auth.apicur.io/realms/apicurio", + "redirectUri": "http://registry.apicur.io", + "clientId": "apicurio-registry-ui" + } + }, + "features": { + "readOnly": false, + "breadcrumbs": true, + "roleManagement": false, + "settings": true + } + } + }, + "UserInterfaceConfigAuth": { + "title": "Root Type for UserInterfaceConfigAuth", + "description": "", + "required": [ + "obacEnabled", + "rbacEnabled" + ], + "type": "object", + "properties": { + "type": { + "enum": [ + "none", + "oidc" + ], + "type": "string" + }, + "rbacEnabled": { + "type": "boolean" + }, + "obacEnabled": { + "type": "boolean" + }, + "options": { + "$ref": "#/components/schemas/Properties" + } + }, + "example": { + "type": "oidc", + "rbacEnabled": true, + "obacEnabled": false, + "options": { + "url": "https://auth.apicur.io/realms/apicurio", + "redirectUri": "https://registry.apicur.io", + "clientId": "registry-ui" + } + } + }, + "UserInterfaceConfigFeatures": { + "title": "Root Type for UserInterfaceConfigFeatures", + "description": "", + "required": [], + "type": "object", + "properties": { + "readOnly": { + "type": "boolean" + }, + "breadcrumbs": { + "type": "boolean" + }, + "roleManagement": { + "type": "boolean" + }, + "settings": { + "type": "boolean" + } + }, + "example": { + "readOnly": false, + "breadcrumbs": true, + "roleManagement": false, + "settings": true + } + }, + "UserInterfaceConfigUi": { + "title": "Root Type for UserInterfaceConfigUi", + "description": "", + "type": "object", + "properties": { + "contextPath": { + "type": "string" + }, + "navPrefixPath": { + "type": "string" + }, + "oaiDocsUrl": { + "type": "string" + } + }, + "example": { + "contextPath": "/", + "navPrefixPath": "/", + "oaiDocsUrl": "https://registry.apicur.io/docs" + } } }, "responses": { diff --git a/ui/.docker-scripts/create-config.cjs b/ui/.docker-scripts/create-config.cjs index 226a47ca35..28c8611c58 100755 --- a/ui/.docker-scripts/create-config.cjs +++ b/ui/.docker-scripts/create-config.cjs @@ -6,10 +6,12 @@ const CONFIG_OUTPUT_PATH=process.env["REGISTRY_CONFIG_OUTPUT_PATH"] || "/opt/app console.info("Generating application config at:", CONFIG_OUTPUT_PATH); -const CONTEXT_PATH=process.env["REGISTRY_CONTEXT_PATH"] || "/"; -const NAV_PREFIX_PATH=process.env["REGISTRY_NAV_PREFIX_PATH"] || "/"; const REGISTRY_API_URL=process.env["REGISTRY_API_URL"] || "http://localhost:8080/apis/registry/v2"; +const CONTEXT_PATH=process.env["REGISTRY_CONTEXT_PATH"]; +const NAV_PREFIX_PATH=process.env["REGISTRY_NAV_PREFIX_PATH"]; +const DOCS_URL=process.env["REGISTRY_DOCS_URL"]; + const AUTH_TYPE=process.env["REGISTRY_AUTH_TYPE"] || "none"; const AUTH_RBAC_ENABLED=process.env["REGISTRY_AUTH_RBAC_ENABLED"] || "false"; const AUTH_OBAC_ENABLED=process.env["REGISTRY_AUTH_OBAC_ENABLED"] || "false"; @@ -29,30 +31,67 @@ const CONFIG = { artifacts: { url: `${REGISTRY_API_URL}` }, - ui: { - contextPath: CONTEXT_PATH, - navPrefixPath: NAV_PREFIX_PATH, - oaiDocsUrl: "/docs/" - }, - auth: { - type: AUTH_TYPE, - rbacEnabled: AUTH_RBAC_ENABLED === "true", - obacEnabled: AUTH_OBAC_ENABLED === "true", - options: { - url: AUTH_URL, - redirectUri: AUTH_REDIRECT_URL, - clientId: AUTH_CLIENT_ID, - scope: AUTH_CLIENT_SCOPES - } - }, - features: { - readOnly: FEATURE_READ_ONLY === "true", - breadcrumbs: FEATURE_BREADCRUMBS === "true", - roleManagement: FEATURE_ROLE_MANAGEMENT === "true", - settings: FEATURE_SETTINGS === "true" - } + ui: {}, + auth: {}, + features: {} }; + +// Configure UI elements +if (CONTEXT_PATH) { + CONFIG.ui.contextPath = CONTEXT_PATH; +} +if (NAV_PREFIX_PATH) { + CONFIG.ui.navPrefixPath = NAV_PREFIX_PATH; +} +if (DOCS_URL) { + CONFIG.ui.oaiDocsUrl = DOCS_URL; +} + + +// Configure auth +if (AUTH_TYPE) { + CONFIG.auth.type = AUTH_TYPE; +} +if (AUTH_RBAC_ENABLED) { + CONFIG.auth.rbacEnabled = AUTH_RBAC_ENABLED === "true"; +} +if (AUTH_OBAC_ENABLED) { + CONFIG.auth.obacEnabled = AUTH_OBAC_ENABLED === "true"; +} + +if (AUTH_TYPE === "oidc") { + CONFIG.auth.options = {}; + if (AUTH_URL) { + CONFIG.auth.options.url = AUTH_URL; + } + if (AUTH_REDIRECT_URL) { + CONFIG.auth.options.redirectUri = AUTH_REDIRECT_URL; + } + if (AUTH_CLIENT_ID) { + CONFIG.auth.options.clientId = AUTHAUTH_CLIENT_ID_URL; + } + if (AUTH_CLIENT_SCOPES) { + CONFIG.auth.options.scope = AUTH_CLIENT_SCOPES; + } +} + +// Configure features +if (FEATURE_READ_ONLY) { + CONFIG.features.readOnly = FEATURE_READ_ONLY === "true"; +} +if (FEATURE_BREADCRUMBS) { + CONFIG.features.breadcrumbs = FEATURE_BREADCRUMBS === "true"; +} +if (FEATURE_ROLE_MANAGEMENT) { + CONFIG.features.roleManagement = FEATURE_ROLE_MANAGEMENT === "true"; +} +if (FEATURE_SETTINGS) { + CONFIG.features.settings = FEATURE_SETTINGS === "true"; +} + + + const FILE_CONTENT = ` const ApicurioRegistryConfig = ${JSON.stringify(CONFIG, null, 4)}; `; diff --git a/ui/ui-app/configs/config-3scale.js b/ui/ui-app/configs/config-3scale.js index 490fd81279..75df4e6edd 100644 --- a/ui/ui-app/configs/config-3scale.js +++ b/ui/ui-app/configs/config-3scale.js @@ -3,24 +3,11 @@ var ApicurioRegistryConfig = { url: "https://registry-api.dev.apicur.io/apis/registry/v3" }, ui: { - contextPath: "/", - navPrefixPath: "/", oaiDocsUrl: "http://localhost:8889" }, auth: { - type: "oidc", - rbacEnabled: true, - obacEnabled: false, options: { - url: "https://sso.dev.apicur.io/realms/apicurio", redirectUri: "http://localhost:8888", - clientId: "registry-ui" } - }, - features: { - readOnly: false, - breadcrumbs: true, - roleManagement: false, - settings: true } }; diff --git a/ui/ui-app/configs/config-local.js b/ui/ui-app/configs/config-local.js new file mode 100644 index 0000000000..749fb1197c --- /dev/null +++ b/ui/ui-app/configs/config-local.js @@ -0,0 +1,5 @@ +var ApicurioRegistryConfig = { + artifacts: { + url: "http://localhost:8080/apis/registry/v3" + } +}; diff --git a/ui/ui-app/configs/config-none.js b/ui/ui-app/configs/config-none.js deleted file mode 100644 index cb860ab235..0000000000 --- a/ui/ui-app/configs/config-none.js +++ /dev/null @@ -1,19 +0,0 @@ -var ApicurioRegistryConfig = { - artifacts: { - url: "http://localhost:8080/apis/registry/v3" - }, - ui: { - contextPath: "/", - navPrefixPath: "/", - oaiDocsUrl: "http://localhost:8889" - }, - auth: { - type: "none" - }, - features: { - readOnly: false, - breadcrumbs: true, - roleManagement: false, - settings: true - } -}; diff --git a/ui/ui-app/configs/config-oidc-rbac.js b/ui/ui-app/configs/config-oidc-rbac.js deleted file mode 100644 index ad6882fb85..0000000000 --- a/ui/ui-app/configs/config-oidc-rbac.js +++ /dev/null @@ -1,62 +0,0 @@ -var ApicurioRegistryConfig = { - artifacts: { - url: "http://localhost:8080/apis/registry/v3" - }, - ui: { - contextPath: "/", - navPrefixPath: "/", - oaiDocsUrl: "http://localhost:8889" - }, - auth: { - type: "oidc", - rbacEnabled: true, - obacEnabled: false, - options: { - url: "https://auth.apicur.io/auth/realms/apicurio-local", - redirectUri: "http://localhost:8888", - clientId: "apicurio-registry-ui" - } - }, - features: { - readOnly: false, - breadcrumbs: true, - roleManagement: true, - settings: true - }, - principals: [ - { - "principalType": "USER_ACCOUNT", - "id": "ewittman", - "displayName": "Eric Wittmann", - "emailAddress": "ewittman@examplecom" - }, - { - "principalType": "USER_ACCOUNT", - "id": "carnal", - "displayName": "Carles Arnal", - "emailAddress": "carnal@example.com" - }, - { - "principalType": "USER_ACCOUNT", - "id": "jsenko", - "displayName": "Jakub Senko", - "emailAddress": "jsenko@examplecom" - }, - { - "principalType": "USER_ACCOUNT", - "id": "famartin", - "displayName": "Fabian Martinez Gonzalez", - "emailAddress": "famartin@examplecom" - }, - { - "principalType": "SERVICE_ACCOUNT", - "id": "svc_account_user-643243-89435625", - "displayName": "Service Account User 1" - }, - { - "principalType": "SERVICE_ACCOUNT", - "id": "svc_account_kafka-12345", - "displayName": "Kafka Service Account" - }, - ] -}; diff --git a/ui/ui-app/configs/config-oidc.js b/ui/ui-app/configs/config-oidc.js deleted file mode 100644 index 3dcec324e1..0000000000 --- a/ui/ui-app/configs/config-oidc.js +++ /dev/null @@ -1,26 +0,0 @@ -var ApicurioRegistryConfig = { - artifacts: { - url: "http://localhost:8080/apis/registry/v3" - }, - ui: { - contextPath: "/", - navPrefixPath: "/", - oaiDocsUrl: "http://localhost:8889" - }, - auth: { - type: "oidc", - rbacEnabled: true, - obacEnabled: false, - options: { - url: "https://auth.apicur.io/auth/realms/apicurio-local", - redirectUri: "http://localhost:8888", - clientId: "apicurio-registry-ui" - } - }, - features: { - readOnly: false, - breadcrumbs: true, - roleManagement: false, - settings: true - } -}; diff --git a/ui/ui-app/init-dev.sh b/ui/ui-app/init-dev.sh index 5246e314d7..99ad034023 100755 --- a/ui/ui-app/init-dev.sh +++ b/ui/ui-app/init-dev.sh @@ -8,7 +8,7 @@ CONFIG_TYPE=$1 if [ "x$CONFIG_TYPE" = "x" ] then - CONFIG_TYPE="none" + CONFIG_TYPE="local" fi cp configs/version.js version.js diff --git a/ui/ui-app/src/app/App.tsx b/ui/ui-app/src/app/App.tsx index 5567e6a879..6496c22330 100644 --- a/ui/ui-app/src/app/App.tsx +++ b/ui/ui-app/src/app/App.tsx @@ -37,6 +37,9 @@ export const App: FunctionComponent = () => { type: config.authType() as "none" | "oidc", options: config.authOptions() }; + if (authConfig.type === "oidc" && (authConfig.options.redirectUri && authConfig.options.redirectUri.startsWith("/"))) { + authConfig.options.redirectUri = window.location.origin + authConfig.options.redirectUri; + } return ( diff --git a/ui/ui-app/src/main.tsx b/ui/ui-app/src/main.tsx index 90e9774c92..28d5318b1a 100644 --- a/ui/ui-app/src/main.tsx +++ b/ui/ui-app/src/main.tsx @@ -1,9 +1,14 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "@app/App.tsx"; +import { ConfigService, useConfigService } from "@services/useConfigService.ts"; -ReactDOM.createRoot(document.getElementById("root")!).render( - - - -); +// eslint-disable-next-line react-hooks/rules-of-hooks +const config: ConfigService = useConfigService(); +config.fetchAndMergeConfigs().then(() => { + ReactDOM.createRoot(document.getElementById("root")!).render( + + + + ); +}); diff --git a/ui/ui-app/src/services/useConfigService.ts b/ui/ui-app/src/services/useConfigService.ts index 4d25d84595..0dc5fb0bff 100644 --- a/ui/ui-app/src/services/useConfigService.ts +++ b/ui/ui-app/src/services/useConfigService.ts @@ -1,3 +1,5 @@ +import { createEndpoint, httpGet } from "@utils/rest.utils.ts"; +import { cloneObject } from "@utils/object.utils.ts"; export enum AlertVariant { success = "success", @@ -136,11 +138,46 @@ export function getRegistryConfig(): ApicurioRegistryConfig { return config; } -const registryConfig: ApicurioRegistryConfig = getRegistryConfig(); +function overrideObject(base: any, overrides: any | undefined): any { + if (overrides === undefined) { + return { + ...base + }; + } + const rval: any = {}; + Object.getOwnPropertyNames(base).forEach(propertyName => { + const baseValue: any = base[propertyName]; + const overrideValue: any = overrides[propertyName]; + if (overrideValue) { + if (typeof baseValue === "object" && typeof overrideValue === "object") { + rval[propertyName] = overrideObject(baseValue, overrideValue); + } else { + rval[propertyName] = overrideValue; + } + } else { + rval[propertyName] = baseValue; + } + }); + return rval; +} + +function overrideConfig(base: ApicurioRegistryConfig, overrides: ApicurioRegistryConfig): ApicurioRegistryConfig { + const rval: ApicurioRegistryConfig = cloneObject(base); + rval.artifacts = { + url: overrides.artifacts.url + }; + rval.ui = overrideObject(rval.ui, overrides.ui); + rval.auth = overrideObject(rval.auth, overrides.auth); + rval.features = overrideObject(rval.features, overrides.features); + return rval; +} + +let registryConfig: ApicurioRegistryConfig = getRegistryConfig(); export interface ConfigService { + fetchAndMergeConfigs(): Promise; artifactsUrl(): string; uiContextPath(): string|undefined; uiOaiDocsUrl(): string; @@ -161,6 +198,21 @@ export interface ConfigService { export class ConfigServiceImpl implements ConfigService { + public fetchAndMergeConfigs(): Promise { + const endpoint: string = createEndpoint(this.artifactsUrl(), "/system/uiConfig"); + console.info("[Config] Fetching UI configuration from: ", endpoint); + return httpGet(endpoint).then(config => { + console.info("[Config] UI configuration fetched successfully: ", config); + registryConfig = overrideConfig(config, registryConfig); + }).catch(error => { + console.error("[Config] Error fetching UI configuration: ", error); + console.error("------------------------------------------"); + console.error("[Config] Note: using local UI config only!"); + console.error("------------------------------------------"); + return Promise.resolve(); + }); + } + public artifactsUrl(): string { return registryConfig.artifacts.url || "http://localhost:8080/apis/registry/v3/"; }