From 9994ed157adcf5e59c5089ca5ff28d87dc8ffea6 Mon Sep 17 00:00:00 2001 From: LP B Date: Wed, 19 Jun 2024 13:44:57 +0200 Subject: [PATCH] fix(app): properly update the app state when losing connectivity to a remote environment while browsing it (#11942) --- app/docker/__module.js | 4 +- app/kubernetes/__module.js | 63 ++++++++++++------- .../dockerMaxApiVersionInterceptor.ts | 8 +-- app/react/portainer/HomeView/HomeView.tsx | 13 +++- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/app/docker/__module.js b/app/docker/__module.js index 12a66d44e..cb9383bcf 100644 --- a/app/docker/__module.js +++ b/app/docker/__module.js @@ -16,7 +16,7 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([ parent: 'endpoint', url: '/docker', abstract: true, - onEnter: /* @ngInject */ function onEnter(endpoint, $async, $state, EndpointService, Notifications, StateManager, SystemService) { + onEnter: /* @ngInject */ function onEnter(endpoint, $async, $state, EndpointService, Notifications, StateManager, SystemService, EndpointProvider) { return $async(async () => { const dockerTypes = [PortainerEndpointTypes.DockerEnvironment, PortainerEndpointTypes.AgentOnDockerEnvironment, PortainerEndpointTypes.EdgeAgentOnDockerEnvironment]; @@ -44,9 +44,11 @@ angular.module('portainer.docker', ['portainer.app', reactModule]).config([ if (endpoint.Type == PortainerEndpointTypes.EdgeAgentOnDockerEnvironment) { params = { redirect: true, environmentId: endpoint.Id, environmentName: endpoint.Name, route: 'docker.dashboard' }; } else { + EndpointProvider.clean(); Notifications.error('Failed loading environment', e); } $state.go('portainer.home', params, { reload: true, inherit: false }); + return false; } async function checkEndpointStatus(endpoint) { diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js index e4fa9fb6a..c4b85e3f2 100644 --- a/app/kubernetes/__module.js +++ b/app/kubernetes/__module.js @@ -63,12 +63,13 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo $state, endpoint, KubernetesHealthService, - KubernetesNamespaceService, Notifications, StateManager, $http, Authentication, - UserService + UserService, + EndpointService, + EndpointProvider ) { return $async(async () => { // if the user wants to use front end cache for performance, set the angular caching settings @@ -93,39 +94,57 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo $state.go('portainer.home'); return; } + try { - if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) { - //edge - try { - await KubernetesHealthService.ping(endpoint.Id); - endpoint.Status = EnvironmentStatus.Up; - } catch (e) { - endpoint.Status = EnvironmentStatus.Down; - } + const status = await checkEndpointStatus( + endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment + ? KubernetesHealthService.ping(endpoint.Id) + : // use selfsubject access review to check if we can connect to the kubernetes environment + // because it gets a fast response, and is accessible to all users + getSelfSubjectAccessReview(endpoint.Id, 'default') + ); + + if (endpoint.Type !== PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) { + await updateEndpointStatus(endpoint, status); + } + endpoint.Status = status; + + if (endpoint.Status === EnvironmentStatus.Down) { + throw new Error( + endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment + ? 'Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.' + : `The environment named ${endpoint.Name} is unreachable.` + ); } await StateManager.updateEndpointState(endpoint); - - if (endpoint.Type === PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment && endpoint.Status === EnvironmentStatus.Down) { - throw new Error('Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.'); - } - - // use selfsubject access review to check if we can connect to the kubernetes environment - // because it's gets a fast response, and is accessible to all users - try { - await getSelfSubjectAccessReview(endpoint.Id, 'default'); - } catch (e) { - throw new Error(`The environment named ${endpoint.Name} is unreachable.`); - } } catch (e) { let params = {}; if (endpoint.Type == PortainerEndpointTypes.EdgeAgentOnKubernetesEnvironment) { params = { redirect: true, environmentId: endpoint.Id, environmentName: endpoint.Name, route: 'kubernetes.dashboard' }; } else { + EndpointProvider.clean(); Notifications.error('Failed loading environment', e); } $state.go('portainer.home', params, { reload: true, inherit: false }); + return false; + } + + async function checkEndpointStatus(promise) { + try { + await promise; + return EnvironmentStatus.Up; + } catch (e) { + return EnvironmentStatus.Down; + } + } + + async function updateEndpointStatus(endpoint, status) { + if (endpoint.Status === status) { + return; + } + await EndpointService.updateEndpoint(endpoint.Id, { Status: status }); } }); }, diff --git a/app/portainer/services/dockerMaxApiVersionInterceptor.ts b/app/portainer/services/dockerMaxApiVersionInterceptor.ts index 267efd98f..f35f16e36 100644 --- a/app/portainer/services/dockerMaxApiVersionInterceptor.ts +++ b/app/portainer/services/dockerMaxApiVersionInterceptor.ts @@ -4,8 +4,6 @@ import { setupCache, buildMemoryStorage } from 'axios-cache-interceptor'; import { buildDockerProxyUrl } from '@/react/docker/proxy/queries/buildDockerProxyUrl'; -import PortainerError from '../error'; - import { MAX_DOCKER_API_VERSION } from './dockerMaxApiVersion'; const envVersionAxios = Axios.create({ @@ -56,9 +54,7 @@ export async function dockerMaxAPIVersionInterceptor( } return config; } catch (err) { - throw new PortainerError( - 'An error occurred while trying to limit request to the maximum supported Docker API version', - err - ); + // if the interceptor errors, return the original config + return rawConfig; } } diff --git a/app/react/portainer/HomeView/HomeView.tsx b/app/react/portainer/HomeView/HomeView.tsx index 6e5e83ad6..5a504601a 100644 --- a/app/react/portainer/HomeView/HomeView.tsx +++ b/app/react/portainer/HomeView/HomeView.tsx @@ -1,6 +1,8 @@ +import { useStore } from 'zustand'; import { useCurrentStateAndParams, useRouter } from '@uirouter/react'; import { useEffect, useState } from 'react'; +import { environmentStore } from '@/react/hooks/current-environment-store'; import { Environment } from '@/react/portainer/environments/types'; import { snapshotEndpoints } from '@/react/portainer/environments/environment.service'; import { isEdgeEnvironment } from '@/react/portainer/environments/utils'; @@ -18,6 +20,8 @@ import { LicenseNodePanel } from './LicenseNodePanel'; import { BackupFailedPanel } from './BackupFailedPanel'; export function HomeView() { + const { clear: clearStore } = useStore(environmentStore); + const { params } = useCurrentStateAndParams(); const [connectingToEdgeEndpoint, setConnectingToEdgeEndpoint] = useState( !!params.redirect @@ -40,14 +44,19 @@ export function HomeView() { endpointId: params.environmentId, }); } else { - router.stateService.go('portainer.home', {}, { inherit: false }); + clearStore(); + router.stateService.go( + 'portainer.home', + {}, + { reload: true, inherit: false } + ); } } if (params.redirect) { redirect(); } - }, [params, setConnectingToEdgeEndpoint, router]); + }, [params, setConnectingToEdgeEndpoint, router, clearStore]); return ( <>